diff --git a/apps/api/src/geocoding/entities/base-place.entity.ts b/apps/api/src/geocoding/entities/base-place.entity.ts new file mode 100644 index 0000000..c1bbc6e --- /dev/null +++ b/apps/api/src/geocoding/entities/base-place.entity.ts @@ -0,0 +1,45 @@ +import { Column, PrimaryGeneratedColumn, CreateDateColumn, Index } from 'typeorm'; + +export abstract class BasePlace { + @PrimaryGeneratedColumn() + id: number; + + @Column({ type: 'decimal', precision: 10, scale: 8, nullable: true }) + latitude: number; + + @Column({ type: 'decimal', precision: 11, scale: 8, nullable: true }) + longitude: number; + + @Column({ nullable: true }) + @Index() + name: string; + + @Column({ nullable: true }) + @Index() + name_ar: string; + + @Column({ nullable: true }) + name_en: string; + + @Column({ nullable: true }) + address: string; + + @Column({ nullable: true }) + category: string; + + @Column({ nullable: true }) + neighbourhood: string; + + @Column({ nullable: true }) + city: string; + + @Column({ type: 'text', nullable: true }) + description: string; + + @CreateDateColumn() + created_at: Date; + + @Column({ type: 'geometry', spatialFeatureType: 'Point', srid: 4326, nullable: true }) + @Index({ spatial: true }) + location: any; +} diff --git a/apps/api/src/geocoding/entities/place-egypt.entity.ts b/apps/api/src/geocoding/entities/place-egypt.entity.ts new file mode 100644 index 0000000..862e1cf --- /dev/null +++ b/apps/api/src/geocoding/entities/place-egypt.entity.ts @@ -0,0 +1,5 @@ +import { Entity } from 'typeorm'; +import { BasePlace } from './base-place.entity'; + +@Entity('places_egypt') +export class PlaceEgypt extends BasePlace {} diff --git a/apps/api/src/geocoding/entities/place-jordan.entity.ts b/apps/api/src/geocoding/entities/place-jordan.entity.ts new file mode 100644 index 0000000..bf67163 --- /dev/null +++ b/apps/api/src/geocoding/entities/place-jordan.entity.ts @@ -0,0 +1,5 @@ +import { Entity } from 'typeorm'; +import { BasePlace } from './base-place.entity'; + +@Entity('places_jordan') +export class PlaceJordan extends BasePlace {} diff --git a/apps/api/src/geocoding/entities/place-syria.entity.ts b/apps/api/src/geocoding/entities/place-syria.entity.ts index a09828e..fc30100 100644 --- a/apps/api/src/geocoding/entities/place-syria.entity.ts +++ b/apps/api/src/geocoding/entities/place-syria.entity.ts @@ -1,43 +1,5 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm'; +import { Entity } from 'typeorm'; +import { BasePlace } from './base-place.entity'; @Entity('places_syria') -export class PlaceSyria { - @PrimaryGeneratedColumn() - id: number; - - @Column({ type: 'decimal', precision: 10, scale: 8, nullable: true }) - latitude: number; - - @Column({ type: 'decimal', precision: 11, scale: 8, nullable: true }) - longitude: number; - - @Column({ nullable: true }) - name: string; - - @Column({ nullable: true }) - name_ar: string; - - @Column({ nullable: true }) - name_en: string; - - @Column({ nullable: true }) - address: string; - - @Column({ nullable: true }) - category: string; - - @Column({ nullable: true }) - neighbourhood: string; - - @Column({ nullable: true }) - city: string; - - @Column({ type: 'text', nullable: true }) - description: string; - - @CreateDateColumn() - created_at: Date; - - @Column({ type: 'geometry', spatialFeatureType: 'Point', srid: 4326, nullable: true }) - location: any; -} +export class PlaceSyria extends BasePlace {} diff --git a/apps/api/src/geocoding/geocoding.controller.ts b/apps/api/src/geocoding/geocoding.controller.ts index 30ceb40..e33dbd1 100644 --- a/apps/api/src/geocoding/geocoding.controller.ts +++ b/apps/api/src/geocoding/geocoding.controller.ts @@ -44,15 +44,15 @@ export class GeocodingController { return this.geocodingService.upsertPlace(placeData); } + @Post('upsert-batch') + @ApiOperation({ summary: 'Add or Update multiple locations in bulk' }) + async upsertBatch(@Body() body: { places: any[] }) { + return this.geocodingService.upsertBatch(body.places); + } + @Get('places') @ApiOperation({ summary: 'Get recent user submitted places' }) async getPlaces(@Query('limit') limit?: number) { return this.geocodingService.getRecentPlaces(limit); } - - @Post('migrate') - @ApiOperation({ summary: 'Migrate legacy MySQL data to PostGIS' }) - async migrate() { - return this.geocodingService.migrateFromMySQL('LEGACY_DB'); - } } diff --git a/apps/api/src/geocoding/geocoding.module.ts b/apps/api/src/geocoding/geocoding.module.ts index 2990884..252d0c0 100644 --- a/apps/api/src/geocoding/geocoding.module.ts +++ b/apps/api/src/geocoding/geocoding.module.ts @@ -4,6 +4,8 @@ import { GeocodingService } from './geocoding.service'; import { GeocodingInitService } from './geocoding-init.service'; import { GeocodingController } from './geocoding.controller'; import { PlaceSyria } from './entities/place-syria.entity'; +import { PlaceJordan } from './entities/place-jordan.entity'; +import { PlaceEgypt } from './entities/place-egypt.entity'; import { OsmArea } from './entities/osm-area.entity'; import { OsmPointWithArea } from './entities/osm-point-with-area.entity'; @@ -11,6 +13,8 @@ import { OsmPointWithArea } from './entities/osm-point-with-area.entity'; imports: [ TypeOrmModule.forFeature([ PlaceSyria, + PlaceJordan, + PlaceEgypt, OsmArea, OsmPointWithArea ]), diff --git a/apps/api/src/geocoding/geocoding.service.ts b/apps/api/src/geocoding/geocoding.service.ts index a4b4710..826ed95 100644 --- a/apps/api/src/geocoding/geocoding.service.ts +++ b/apps/api/src/geocoding/geocoding.service.ts @@ -1,7 +1,10 @@ import { Injectable, Logger, HttpException, HttpStatus } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, Like } from 'typeorm'; +import { Repository } from 'typeorm'; import { PlaceSyria } from './entities/place-syria.entity'; +import { PlaceJordan } from './entities/place-jordan.entity'; +import { PlaceEgypt } from './entities/place-egypt.entity'; +import { BasePlace } from './entities/base-place.entity'; import { OsmArea } from './entities/osm-area.entity'; import { OsmPointWithArea } from './entities/osm-point-with-area.entity'; @@ -11,221 +14,148 @@ export class GeocodingService { constructor( @InjectRepository(PlaceSyria) - private placesRepository: Repository, + private placesSyriaRepository: Repository, + @InjectRepository(PlaceJordan) + private placesJordanRepository: Repository, + @InjectRepository(PlaceEgypt) + private placesEgyptRepository: Repository, @InjectRepository(OsmArea) private osmAreasRepository: Repository, @InjectRepository(OsmPointWithArea) private osmPointsRepository: Repository, ) {} + /** + * تحديد المستودع المناسب بناءً على الإحداثيات الجغرافية + */ + private getRepositoryForCoords(lat: number, lng: number): Repository { + if (lat >= 29 && lat <= 33.5 && lng >= 34.5 && lng <= 39.5) { + if (lat > 32.5 && lng > 35.8) return (this.placesSyriaRepository as unknown) as Repository; + return (this.placesJordanRepository as unknown) as Repository; + } + if (lat >= 22 && lat <= 32 && lng >= 24.5 && lng <= 37) { + return (this.placesEgyptRepository as unknown) as Repository; + } + return (this.placesSyriaRepository as unknown) as Repository; + } + + private getTableNameForRepo(repo: Repository): string { + if (repo === (this.placesJordanRepository as unknown)) return 'places_jordan'; + if (repo === (this.placesEgyptRepository as unknown)) return 'places_egypt'; + return 'places_syria'; + } + async searchPlaces(query: string, lat?: number, lon?: number, radius: number = 20000) { try { - if (!query || query.length < 3) return { results: [] }; - - // Clean query for similarity const cleanQuery = query.trim(); const hasLocation = lat !== undefined && lon !== undefined; + + // If we have no location, we search ALL three tables (Syria, Jordan, Egypt) + // وإلا فسنقوم بالبحث في المنطقة المناسبة فقط لزيادة السرعة والدقة + const tables = hasLocation ? [this.getTableNameForRepo(this.getRepositoryForCoords(lat, lon))] : ['places_syria', 'places_jordan', 'places_egypt']; + + const allResults: any[] = []; + const ILikeQuery = `%${cleanQuery}%`; - // 1. User Submitted Places - const userPlacesQuery = ` - SELECT - id, name, name_ar, name_en, category, latitude, longitude, address, 'user_submitted' as source, + for (const tableName of tables) { + const repo = tableName === 'places_jordan' ? this.placesJordanRepository : + tableName === 'places_egypt' ? this.placesEgyptRepository : + this.placesSyriaRepository; + + const userPlacesQuery = ` + SELECT id, name, name_ar, name_en, category, latitude, longitude, address, 'user_submitted' as source, CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3, $2), 4326)) ELSE 0 END as distance, similarity(COALESCE(name_ar, ''), $1) + similarity(COALESCE(name_en, ''), $1) as relevance - FROM places_syria - WHERE (name_ar % $1 OR name_en % $1 OR name % $1 OR name_ar ILIKE $4 OR name_en ILIKE $4) - ${hasLocation ? `AND location && ST_Expand(ST_SetSRID(ST_MakePoint($3, $2), 4326), $5 / 111320.0)` : ''} - ${hasLocation ? `AND ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3, $2), 4326)) <= $5` : ''} - ORDER BY distance ASC, relevance DESC - LIMIT 10 - `; + FROM ${tableName} + WHERE (name_ar % $1 OR name_en % $1 OR name % $1 OR name_ar ILIKE $4 OR name_en ILIKE $4) + ${hasLocation ? `AND ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3, $2), 4326)) <= $5` : ''} + ORDER BY distance ASC, relevance DESC LIMIT 10 + `; - // 2. OSM Areas - const osmAreasQuery = ` - SELECT - id, name, name_ar, '' as name_en, place_type as category, latitude, longitude, '' as address, 'osm_area' as source, - CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(geom, ST_SetSRID(ST_MakePoint($3, $2), 4326)) ELSE 0 END as distance, - similarity(COALESCE(name_ar, ''), $1) + similarity(COALESCE(name, ''), $1) as relevance - FROM osm_areas - WHERE (name_ar % $1 OR name % $1 OR name_ar ILIKE $4 OR name ILIKE $4) - ${hasLocation ? `AND geom && ST_Expand(ST_SetSRID(ST_MakePoint($3, $2), 4326), $5 / 111320.0)` : ''} - ${hasLocation ? `AND ST_DistanceSphere(geom, ST_SetSRID(ST_MakePoint($3, $2), 4326)) <= $5` : ''} - ORDER BY distance ASC, relevance DESC - LIMIT 10 - `; + const params = [cleanQuery, lat || null, lon || null, ILikeQuery, radius]; + const results = await repo.query(userPlacesQuery, params); + allResults.push(...results); + } - // 3. OSM Points - const osmPointsQuery = ` - SELECT - osm_id as id, name, name_ar, name_en, (COALESCE(amenity, shop, 'poi')) as category, latitude, longitude, - (COALESCE(addr_street, '') || ' ' || COALESCE(neighbourhood_name, '') || ' ' || COALESCE(city_name, '')) as address, - 'osm_point' as source, - CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(geom, ST_SetSRID(ST_MakePoint($3, $2), 4326)) ELSE 0 END as distance, - similarity(COALESCE(name_ar, ''), $1) + similarity(COALESCE(name_en, ''), $1) as relevance - FROM osm_points_with_area - WHERE (name_ar % $1 OR name_en % $1 OR name % $1 OR name_ar ILIKE $4 OR name_en ILIKE $4) - ${hasLocation ? `AND geom && ST_Expand(ST_SetSRID(ST_MakePoint($3, $2), 4326), $5 / 111320.0)` : ''} - ${hasLocation ? `AND ST_DistanceSphere(geom, ST_SetSRID(ST_MakePoint($3, $2), 4326)) <= $5` : ''} - ORDER BY distance ASC, relevance DESC - LIMIT 20 - `; + // Sort combined results by relevance and distance + const sortedResults = allResults.sort((a, b) => b.relevance - a.relevance || a.distance - b.distance).slice(0, 15); - const ILikeQuery = `%${cleanQuery}%`; - const numLat = lat !== undefined ? Number(lat) : null; - const numLon = lon !== undefined ? Number(lon) : null; - const params = [cleanQuery, numLat, numLon, ILikeQuery, radius]; - - const [uPlaces, oAreas, oPoints] = await Promise.all([ - this.placesRepository.query(userPlacesQuery, params), - this.osmAreasRepository.query(osmAreasQuery, params), - this.osmPointsRepository.query(osmPointsQuery, params), - ]); - - const results = [...uPlaces, ...oAreas, ...oPoints].map(p => ({ - ...p, - location: { lat: p.latitude, lng: p.longitude }, - distance: p.distance ? Math.round(parseFloat(p.distance)) : 0, - distance_km: p.distance ? (parseFloat(p.distance) / 1000).toFixed(2) : "0" - })); - - // Re-sort combined results by distance primarily if location is given - results.sort((a, b) => { - if (hasLocation) { - if (a.distance !== b.distance) return a.distance - b.distance; - } - return b.relevance - a.relevance; - }); - - return { results }; + return { results: sortedResults }; } catch (e) { this.logger.error('Search failed:', e); return { results: [] }; } } - /** - * Migrate data from legacy MySQL tables to new PostGIS structure. - * دمج البيانات من MySQL إلى PostgreSQL - */ - async migrateFromMySQL(mysqlUrl: string) { - this.logger.log(`Starting migration from MySQL: ${mysqlUrl}`); - // This logic handles pulling from places_syria and osm_areas into the local Postgres - // In a real scenario, this would use a temporary secondary connection. - // For now, I provide the logic that maps all the metadata correctly. - return { - message: "Infrastructure ready for migration.", - hint: "Use the provided scp script to push your MySQL dump to the database container directly." - }; - } - async reverseGeocode(lat: number, lng: number) { try { - // 1. Search in user-submitted places - const userPlacesQuery = ` - SELECT - id, name, name_ar, category, latitude, longitude, address, 'user_place' as source, - ST_DistanceSphere(location::geometry, ST_SetSRID(ST_MakePoint($1, $2), 4326)) as distance - FROM places_syria - WHERE location IS NOT NULL - ORDER BY location::geometry <-> ST_SetSRID(ST_MakePoint($1, $2), 4326) ASC - LIMIT 3 + const repo = this.getRepositoryForCoords(lat, lng); + const tableName = this.getTableNameForRepo(repo); + const query = ` + SELECT id, name, name_ar, category, latitude, longitude, address, 'user_place' as source, + ST_DistanceSphere(location::geometry, ST_SetSRID(ST_MakePoint($1, $2), 4326)) as distance + FROM ${tableName} WHERE location IS NOT NULL + ORDER BY location::geometry <-> ST_SetSRID(ST_MakePoint($1, $2), 4326) ASC LIMIT 3 `; - const userPlaces = await this.placesRepository.query(userPlacesQuery, [lng, lat]); - - // 2. Search in OSM areas (cities, neighbourhoods) - const osmAreasQuery = ` - SELECT - id, name, name_ar, place_type as category, latitude, longitude, '' as address, 'osm_area' as source, - ST_DistanceSphere(geom::geometry, ST_SetSRID(ST_MakePoint($1, $2), 4326)) as distance - FROM osm_areas - WHERE geom IS NOT NULL - ORDER BY geom::geometry <-> ST_SetSRID(ST_MakePoint($1, $2), 4326) ASC - LIMIT 3 - `; - const osmAreas = await this.osmAreasRepository.query(osmAreasQuery, [lng, lat]); - - return [...userPlaces, ...osmAreas].sort((a, b) => a.distance - b.distance); + return await repo.query(query, [lng, lat]); } catch (error) { this.logger.error('Reverse geocoding error:', error); return []; } } - async addPlace(data: Partial) { + async addPlace(data: Partial) { try { - // 1. Basic validation - if (!data.latitude || !data.longitude) { - throw new HttpException('Latitude and Longitude are required', HttpStatus.BAD_REQUEST); - } + const lat = Number(data.latitude); + const lng = Number(data.longitude); + const repo = this.getRepositoryForCoords(lat, lng); + const tableName = this.getTableNameForRepo(repo); - // 2. Fix flipped coordinates if necessary (Damascus/Amman are Lat 31-36, Lng 35-40) - // If user sends Lat > 40 and Lng < 30, they are likely flipped - let lat = Number(data.latitude); - let lng = Number(data.longitude); - if (lat > 35 && lng < 33) { - this.logger.warn(`Coordinates for ${data.name} seem flipped. Auto-correcting...`); - [lat, lng] = [lng, lat]; - } + const newPlace = repo.create({ ...data, latitude: lat, longitude: lng, created_at: new Date() }); + const savedPlace = await repo.save(newPlace); - // 3. Create the entity - const newPlace = this.placesRepository.create({ - ...data, - latitude: lat, - longitude: lng, - created_at: new Date(), - }); - - // 4. Save and return (The 'location' will be synced either via Save or manual query) - const savedPlace = await this.placesRepository.save(newPlace); - - // 5. Manually force sync the ST_Point location to ensure it's queryable immediately - await this.placesRepository.query( - `UPDATE places_syria SET location = ST_SetSRID(ST_MakePoint($1, $2), 4326) WHERE id = $3`, + await repo.query( + `UPDATE ${tableName} SET location = ST_SetSRID(ST_MakePoint($1, $2), 4326) WHERE id = $3`, [lng, lat, savedPlace.id], ); return { ...savedPlace, latitude: lat, longitude: lng }; } catch (error) { this.logger.error('Failed to add place:', error.message); - throw new HttpException(error.message || 'Internal server error', HttpStatus.INTERNAL_SERVER_ERROR); + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); } } - async upsertPlace(data: Partial) { + async upsertPlace(data: Partial) { try { - if (!data.latitude || !data.longitude || (!data.name && !data.name_ar)) { - throw new HttpException('Missing required fields for upsert', HttpStatus.BAD_REQUEST); - } - - const lng = Number(data.longitude); const lat = Number(data.latitude); + const lng = Number(data.longitude); + const repo = this.getRepositoryForCoords(lat, lng); + const tableName = this.getTableNameForRepo(repo); - // 1. Check for spatial match (within 10 meters) - const existingQuery = ` - SELECT id FROM places_syria - WHERE ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1, $2), 4326)) < 10 - LIMIT 1 - `; - const existing = await this.placesRepository.query(existingQuery, [lng, lat]); + const existing = await repo.query( + `SELECT id, name, name_ar FROM ${tableName} WHERE ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1, $2), 4326)) < 15 LIMIT 1`, + [lng, lat], + ); if (existing && existing.length > 0) { const id = existing[0].id; - this.logger.log(`Spatial match found for ${data.name || data.name_ar} (ID: ${id}). Updating...`); + const currentName = existing[0].name_ar || existing[0].name || ''; + const newName = data.name_ar || data.name || ''; - await this.placesRepository.update(id, { - name: data.name || undefined, - name_ar: data.name_ar || undefined, - address: data.address || undefined, + // منطق "الاسم الأفضل": تحديث الاسم فقط إذا كان الجديد أطول أو يحتوي على تفاصيل أكثر + const shouldUpdateName = newName.length > currentName.length || (newName.includes('منزل') && !currentName.includes('منزل')); + + await repo.update(id, { + name: shouldUpdateName ? (data.name || undefined) : undefined, + name_ar: shouldUpdateName ? (data.name_ar || undefined) : undefined, category: data.category || undefined, - description: data.description || undefined, city: data.city || undefined, - neighbourhood: data.neighbourhood || undefined }); return { id, action: 'updated' }; } - // 2. No matching place found, create new one const result = await this.addPlace(data); return { id: result.id, action: 'created' }; } catch (error) { @@ -234,15 +164,30 @@ export class GeocodingService { } } - async getRecentPlaces(limit: number = 50) { - try { - return this.placesRepository.find({ - order: { created_at: 'DESC' }, - take: limit, - }); - } catch (error) { - this.logger.error('Failed to fetch recent places:', error); - return []; + async upsertBatch(places: Partial[]) { + const results: { id: number; action: string }[] = []; + for (const place of places) { + try { + const res = await this.upsertPlace(place); + results.push(res as { id: number; action: string }); + } catch (e) { + this.logger.error(`Batch item failed: ${place.name || place.name_ar}`, e.message); + } } + return { + total: places.length, processed: results.length, + created: results.filter(r => r.action === 'created').length, + updated: results.filter(r => r.action === 'updated').length + }; + } + + async getRecentPlaces(limit: number = 50) { + const syria = await this.placesSyriaRepository.find({ order: { created_at: 'DESC' }, take: limit }); + const jordan = await this.placesJordanRepository.find({ order: { created_at: 'DESC' }, take: limit }); + const egypt = await this.placesEgyptRepository.find({ order: { created_at: 'DESC' }, take: limit }); + + return [...syria, ...jordan, ...egypt] + .sort((a, b) => b.created_at.getTime() - a.created_at.getTime()) + .slice(0, limit); } } diff --git a/apps/web/public/map-demo.html b/apps/web/public/map-demo.html index b790b87..7f9fee6 100644 --- a/apps/web/public/map-demo.html +++ b/apps/web/public/map-demo.html @@ -3,59 +3,164 @@ - Intaleq Premium Map - خرائط انطلاقة الذكية + Intaleq Premium Map V3 - خرائط انطلاقة الذكية
-
جاري معالجة المسار...
+ +
+
+
+ جاري حساب المسار الذكي... +
+
- Logo - POWERED BY INTALEQ + Logo + INTALEQ PREMIUM MAPS
-

🗺️ خرائط انطلاقة الذكية

-

المسار المعتمد: عمان ➔ دمشق

+
Version 3.0.0
+

🗺️ خرائط انطلاقة الذكية

+

حلول جغرافية متقدمة لمنطقة الشرق الأوسط وشمال أفريقيا بدقة متناهية ونظام عرض ثلاثي الأبعاد.

- +
+ + + +
@@ -63,54 +168,38 @@ + diff --git a/apps/web/public/style.json b/apps/web/public/style.json index cda1ae6..edcd0f0 100644 --- a/apps/web/public/style.json +++ b/apps/web/public/style.json @@ -1,159 +1,349 @@ { "version": 8, - "name": "Intaleq Modern Premium", - "metadata": {}, + "name": "Intaleq Premium Map Style v3", + "metadata": { + "brand": "Intaleq", + "version": "3.0.0", + "description": "Warm MENA palette · Google+OSM hybrid · 3D buildings with light · LOD zoom system · Full layer set" + }, "center": [36.276008, 33.513685], "zoom": 15, + + "light": { + "anchor": "viewport", + "color": "#ffffff", + "intensity": 0.45, + "position": [1.5, 225, 30] + }, + "glyphs": "https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf", "sprite": "https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite", + "sources": { "local-osm-polygons": { "type": "vector", - "tiles": [ - "https://tiles.intaleqapp.com/planet_osm_polygon/{z}/{x}/{y}" - ], + "tiles": ["https://tiles.intaleqapp.com/planet_osm_polygon/{z}/{x}/{y}"], "maxzoom": 14, - "attribution": "© Intaleq Mapping Solutions" + "attribution": "© Intaleq | © OpenStreetMap contributors" }, "local-osm-lines": { "type": "vector", - "tiles": [ - "https://tiles.intaleqapp.com/planet_osm_line/{z}/{x}/{y}" - ], + "tiles": ["https://tiles.intaleqapp.com/planet_osm_line/{z}/{x}/{y}"], "maxzoom": 14 }, "local-osm-points": { "type": "vector", - "tiles": [ - "https://tiles.intaleqapp.com/planet_osm_point/{z}/{x}/{y}" - ], + "tiles": ["https://tiles.intaleqapp.com/planet_osm_point/{z}/{x}/{y}"], + "maxzoom": 14 + }, + "places_egypt": { + "type": "vector", + "tiles": ["https://tiles.intaleqapp.com/places_egypt/{z}/{x}/{y}"], "maxzoom": 14 } }, + "layers": [ + { "id": "background", "type": "background", "paint": { - "background-color": "#f8f9fa" + "background-color": "#F2EDE4" } }, + { - "id": "water-layer", + "id": "landuse-residential", "type": "fill", "source": "local-osm-polygons", "source-layer": "planet_osm_polygon", - "filter": [ - "in", - "natural", - "water", - "lake", - "riverbank" - ], + "filter": ["==", "landuse", "residential"], "paint": { - "fill-color": "#a3ccff" + "fill-color": "#EDE7DC", + "fill-opacity": 1 } }, + { + "id": "landuse-commercial", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": ["==", "landuse", "commercial"], + "paint": { + "fill-color": "#F5EED8", + "fill-opacity": 1 + } + }, + { + "id": "landuse-industrial", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": ["in", "landuse", "industrial", "railway"], + "paint": { + "fill-color": "#DDD8CC", + "fill-opacity": 1 + } + }, + { + "id": "landuse-cemetery", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": ["==", "landuse", "cemetery"], + "paint": { + "fill-color": "#B0C8A8", + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-military", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": ["==", "landuse", "military"], + "paint": { + "fill-color": "#D8CEB8", + "fill-opacity": 0.75 + } + }, + { "id": "park-layer", "type": "fill", "source": "local-osm-polygons", "source-layer": "planet_osm_polygon", "filter": [ - "in", - "leisure", - "park", - "garden", - "nature_reserve", - "pitch" + "any", + ["in", "leisure", "park", "garden", "nature_reserve", "pitch", "playground"], + ["in", "landuse", "grass", "meadow", "forest"], + ["in", "natural", "wood", "scrub", "heath"] ], "paint": { - "fill-color": "#dcedc8" + "fill-color": [ + "match", ["get", "leisure"], + "pitch", "#8DC88C", + "playground", "#A8D8A0", + "#B4D8A4" + ], + "fill-opacity": 0.8 } }, { - "id": "landuse-layer", + "id": "park-outline", + "type": "line", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": ["in", "leisure", "park", "garden", "nature_reserve"], + "paint": { + "line-color": "#80B880", + "line-width": 0.8, + "line-opacity": 0.65 + } + }, + + { + "id": "water-polygon", "type": "fill", "source": "local-osm-polygons", "source-layer": "planet_osm_polygon", "filter": [ - "in", - "landuse", - "residential", - "commercial", - "industrial", - "cemetery" + "any", + ["in", "natural", "water", "lake", "bay"], + ["==", "waterway", "riverbank"], + ["in", "landuse", "basin", "reservoir"], + ["==", "amenity", "fountain"] ], "paint": { - "fill-color": [ - "match", - [ - "get", - "landuse" - ], - "residential", - "#f1f3f4", - "commercial", - "#f8f9fa", - "industrial", - "#f1f3f4", - "cemetery", - "#dcedc8", - "#f1f3f4" + "fill-color": "#7EC8DF", + "fill-opacity": 0.92 + } + }, + { + "id": "water-polygon-outline", + "type": "line", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + ["in", "natural", "water", "lake", "bay"], + ["==", "waterway", "riverbank"], + ["in", "landuse", "basin", "reservoir"] + ], + "paint": { + "line-color": "#50A8CC", + "line-width": 0.8, + "line-opacity": 0.75 + } + }, + + { + "id": "waterway-river", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + ["in", "waterway", "river", "canal"], + ["!=", "intermittent", "yes"], + ["!=", "seasonal", "yes"], + ["!=", "tunnel", "yes"] + ], + "paint": { + "line-color": "#50B8D4", + "line-width": ["interpolate", ["linear"], ["zoom"], + 8, 0.8, 10, 1.5, 14, 4, 16, 7 + ], + "line-opacity": 0.92 + } + }, + { + "id": "waterway-stream-drain", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 13, + "filter": [ + "all", + ["in", "waterway", "stream", "drain", "ditch"], + ["!=", "intermittent", "yes"], + ["!=", "seasonal", "yes"], + ["!=", "tunnel", "yes"] + ], + "paint": { + "line-color": "#68C0D8", + "line-width": ["interpolate", ["linear"], ["zoom"], + 13, 0.7, 16, 2.5 + ], + "line-opacity": 0.8 + } + }, + + { + "id": "railway-area", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": ["==", "landuse", "railway"], + "paint": { + "fill-color": "#D0CBBF", + "fill-opacity": 0.85 + } + }, + + { + "id": "railway-rail-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 8, + "filter": [ + "all", + ["in", "railway", "rail", "narrow_gauge", "preserved"], + ["!=", "service", "yard"], + ["!=", "service", "siding"] + ], + "paint": { + "line-color": "#A8A090", + "line-width": ["interpolate", ["linear"], ["zoom"], + 8, 1.5, 12, 3.5, 16, 7 ] } }, { - "id": "building-3d", - "type": "fill-extrusion", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "minzoom": 15, + "id": "railway-rail-core", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 8, "filter": [ - "has", - "building" + "all", + ["in", "railway", "rail", "narrow_gauge", "preserved"], + ["!=", "service", "yard"], + ["!=", "service", "siding"] ], - "layout": { - "visibility": "visible" - }, "paint": { - "fill-extrusion-color": "#e8eaed", - "fill-extrusion-height": 20, - "fill-extrusion-base": 0, - "fill-extrusion-opacity": 0.8 + "line-color": "#6A6860", + "line-width": ["interpolate", ["linear"], ["zoom"], + 8, 0.8, 12, 2, 16, 4 + ], + "line-dasharray": [6, 4] } }, + + { + "id": "railway-subway-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 10, + "filter": ["in", "railway", "subway", "light_rail", "tram", "monorail"], + "paint": { + "line-color": [ + "match", ["get", "railway"], + "subway", "#A01828", + "light_rail", "#005099", + "tram", "#660EA0", + "monorail", "#006844", + "#883040" + ], + "line-width": ["interpolate", ["linear"], ["zoom"], + 10, 3, 14, 6, 16, 10 + ] + } + }, + { + "id": "railway-subway-core", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 10, + "filter": ["in", "railway", "subway", "light_rail", "tram", "monorail"], + "paint": { + "line-color": [ + "match", ["get", "railway"], + "subway", "#E8253A", + "light_rail", "#1A80F0", + "tram", "#9932DC", + "monorail", "#00A858", + "#E03048" + ], + "line-width": ["interpolate", ["linear"], ["zoom"], + 10, 1.5, 14, 3.5, 16, 6 + ] + } + }, + + { + "id": "road-casing-track-path", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 14, + "filter": ["in", "highway", "track", "path", "footway", "cycleway", "steps"], + "paint": { + "line-color": "#C0B8A8", + "line-width": ["interpolate", ["linear"], ["zoom"], + 14, 0.8, 16, 3 + ], + "line-dasharray": [4, 3], + "line-opacity": 0.7 + } + }, + { "id": "road-casing-minor", "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "residential", - "service", - "unclassified", - "living_street", - "pedestrian", - "path", - "track" - ], + "filter": ["in", "highway", "residential", "service", "unclassified", "living_street", "pedestrian"], "paint": { - "line-color": "#d4d4d4", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 13, - 1, - 16, - 8 - ] + "line-color": "#C8C0B0", + "line-width": ["interpolate", ["linear"], ["zoom"], + 12, 1.5, 16, 10 + ], + "line-opacity": 0.65 } }, { @@ -161,60 +351,27 @@ "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "residential", - "service", - "unclassified", - "living_street", - "pedestrian", - "path", - "track" - ], + "filter": ["in", "highway", "residential", "service", "unclassified", "living_street", "pedestrian"], "paint": { - "line-color": "#ffffff", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 13, - 0.5, - 16, - 6 + "line-color": "#FDFAF5", + "line-width": ["interpolate", ["linear"], ["zoom"], + 12, 0.8, 16, 8 ] } }, + { "id": "road-casing-tertiary", "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "tertiary", - "tertiary_link" - ], + "filter": ["in", "highway", "tertiary", "tertiary_link"], "paint": { - "line-color": "#e0e0e0", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 12, - 1.5, - 16, - 12 - ] + "line-color": "#BEB5A4", + "line-width": ["interpolate", ["linear"], ["zoom"], + 11, 2, 16, 14 + ], + "line-opacity": 0.7 } }, { @@ -222,55 +379,27 @@ "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "tertiary", - "tertiary_link" - ], + "filter": ["in", "highway", "tertiary", "tertiary_link"], "paint": { - "line-color": "#ffffff", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 12, - 1, - 16, - 9 + "line-color": "#FDFAF5", + "line-width": ["interpolate", ["linear"], ["zoom"], + 11, 1.2, 16, 11 ] } }, + { "id": "road-casing-secondary", "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "secondary", - "secondary_link" - ], + "filter": ["in", "highway", "secondary", "secondary_link"], "paint": { - "line-color": "#cfd8dc", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 12, - 2, - 16, - 14 - ] + "line-color": "#B8B0A0", + "line-width": ["interpolate", ["linear"], ["zoom"], + 11, 2.5, 16, 16 + ], + "line-opacity": 0.72 } }, { @@ -278,55 +407,27 @@ "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "secondary", - "secondary_link" - ], + "filter": ["in", "highway", "secondary", "secondary_link"], "paint": { - "line-color": "#f1f5f9", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 12, - 1.5, - 16, - 11 + "line-color": "#FDF8F0", + "line-width": ["interpolate", ["linear"], ["zoom"], + 11, 1.8, 16, 13 ] } }, + { "id": "road-casing-primary", "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "primary", - "primary_link" - ], + "filter": ["in", "highway", "primary", "primary_link"], "paint": { - "line-color": "#facc15", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 12, - 3, - 16, - 16 - ] + "line-color": "#C0A048", + "line-width": ["interpolate", ["linear"], ["zoom"], + 10, 3, 16, 18 + ], + "line-opacity": 0.68 } }, { @@ -334,57 +435,27 @@ "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "primary", - "primary_link" - ], + "filter": ["in", "highway", "primary", "primary_link"], "paint": { - "line-color": "#fefce8", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 12, - 2, - 16, - 12 + "line-color": "#F5D44C", + "line-width": ["interpolate", ["linear"], ["zoom"], + 10, 2, 16, 14 ] } }, + { "id": "road-casing-motorway-trunk", "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "motorway", - "motorway_link", - "trunk", - "trunk_link" - ], + "filter": ["in", "highway", "motorway", "motorway_link", "trunk", "trunk_link"], "paint": { - "line-color": "#fb923c", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 12, - 4, - 16, - 18 - ] + "line-color": "#B89038", + "line-width": ["interpolate", ["linear"], ["zoom"], + 9, 4, 16, 20 + ], + "line-opacity": 0.72 } }, { @@ -392,91 +463,502 @@ "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "motorway", - "motorway_link", - "trunk", - "trunk_link" - ], + "filter": ["in", "highway", "motorway", "motorway_link", "trunk", "trunk_link"], "paint": { - "line-color": "#ffedd5", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 12, - 2.5, - 16, - 14 + "line-color": "#E8C030", + "line-width": ["interpolate", ["linear"], ["zoom"], + 9, 2.5, 16, 16 ] } }, + { - "id": "road-labels", + "id": "building-fill-flat", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": ["has", "building"], + "maxzoom": 14, + "paint": { + "fill-color": "#CEC8BC", + "fill-opacity": ["interpolate", ["linear"], ["zoom"], + 10, 0.4, 13, 0.72 + ], + "fill-outline-color": "#B8B0A4" + } + }, + + { + "id": "building-3d", + "type": "fill-extrusion", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 14, + "filter": ["has", "building"], + "paint": { + "fill-extrusion-color": [ + "match", ["get", "building"], + "commercial", "#D4C8B0", + "retail", "#DDD0B4", + "industrial", "#C8C4B8", + "church", "#D0C4DC", + "mosque", "#C4D8C0", + "hospital", "#DCC8C8", + "school", "#D0D8BC", + "university", "#C8D0B8", + "hotel", "#C8CCE0", + "apartments", "#D0CCC4", + "#CCC8C0" + ], + "fill-extrusion-height": [ + "interpolate", ["linear"], ["zoom"], + 14, ["*", ["coalesce", ["to-number", ["get", "building:levels"], null], 3], 2.0], + 15, ["*", ["coalesce", ["to-number", ["get", "building:levels"], null], 3], 3.0], + 17, ["coalesce", + ["to-number", ["get", "height"], null], + ["*", ["to-number", ["get", "building:levels"], 3], 3.5], + 12 + ] + ], + "fill-extrusion-base": [ + "coalesce", + ["to-number", ["get", "min_height"], null], + 0 + ], + "fill-extrusion-opacity": [ + "interpolate", ["linear"], ["zoom"], + 14, 0.55, + 15, 0.78, + 16, 0.92 + ], + "fill-extrusion-vertical-gradient": true + } + }, + + { + "id": "railway-label", "type": "symbol", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "minzoom": 15, + "minzoom": 13, + "filter": ["in", "railway", "rail", "subway", "light_rail", "tram"], "layout": { - "text-field": "{name}", - "text-font": [ - "Noto Sans Regular" + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 13, 9, 16, 12 ], - "text-size": 13, "symbol-placement": "line", - "text-letter-spacing": 0.05, - "text-padding": 5, + "text-padding": 6, + "text-allow-overlap": false + }, + "paint": { + "text-color": [ + "match", ["get", "railway"], + "subway", "#B01820", + "light_rail", "#005099", + "tram", "#660EA0", + "#4A4840" + ], + "text-halo-color": "rgba(242, 237, 228, 0.95)", + "text-halo-width": 2, + "text-halo-blur": 0.5 + } + }, + + { + "id": "waterway-label", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 12, + "filter": [ + "all", + ["in", "waterway", "river", "canal"], + ["!=", "intermittent", "yes"], + ["has", "name"] + ], + "layout": { + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 12, 10, 16, 13 + ], + "symbol-placement": "line", + "text-letter-spacing": 0.1 + }, + "paint": { + "text-color": "#2878A0", + "text-halo-color": "rgba(255, 255, 255, 0.88)", + "text-halo-width": 2, + "text-halo-blur": 0.5 + } + }, + + { + "id": "building-number-polygon", + "type": "symbol", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 17, + "filter": ["has", "addr:housenumber"], + "layout": { + "text-field": "{addr:housenumber}", + "text-font": ["Noto Sans Regular"], + "text-size": 10, "text-allow-overlap": false, "text-ignore-placement": false }, "paint": { - "text-color": "#3c4043", - "text-halo-color": "rgba(255, 255, 255, 0.8)", - "text-halo-width": 2 + "text-color": "#585040", + "text-halo-color": "rgba(255, 255, 255, 0.95)", + "text-halo-width": 1.5, + "text-halo-blur": 0.3 } }, { - "id": "place-labels", + "id": "building-number-point", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 17, + "filter": ["has", "addr:housenumber"], + "layout": { + "text-field": "{addr:housenumber}", + "text-font": ["Noto Sans Regular"], + "text-size": 10, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#585040", + "text-halo-color": "rgba(255, 255, 255, 0.95)", + "text-halo-width": 1.5, + "text-halo-blur": 0.3 + } + }, + + { + "id": "road-labels-minor", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 16, + "filter": ["in", "highway", "residential", "service", "unclassified", "living_street"], + "layout": { + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 16, 10, 18, 12 + ], + "symbol-placement": "line", + "text-letter-spacing": 0.04, + "text-padding": 4, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#4A4438", + "text-halo-color": "rgba(253, 250, 245, 0.92)", + "text-halo-width": 1.5, + "text-halo-blur": 0.5 + } + }, + { + "id": "road-labels-major", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 13, + "filter": ["in", "highway", "primary", "secondary", "tertiary", "motorway", "trunk"], + "layout": { + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 13, 10, 16, 13, 18, 15 + ], + "symbol-placement": "line", + "text-letter-spacing": 0.05, + "text-padding": 5, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#302820", + "text-halo-color": "rgba(253, 250, 245, 0.94)", + "text-halo-width": 2, + "text-halo-blur": 0.5 + } + }, + + { + "id": "poi-hospital", "type": "symbol", "source": "local-osm-points", "source-layer": "planet_osm_point", "minzoom": 13, + "filter": ["==", "amenity", "hospital"], "layout": { - "text-field": "{name}", - "text-font": [ - "Noto Sans Regular" + "icon-image": "hospital", + "icon-size": ["interpolate", ["linear"], ["zoom"], + 13, 0.7, 16, 1.0 ], - "text-size": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 13, - 12, - 16, - 16 - ], - "text-offset": [ - 0, - 1.5 + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 13, 9, 16, 12 ], + "text-offset": [0, 1.2], "text-anchor": "top", - "visibility": "visible" + "text-allow-overlap": false }, "paint": { - "text-color": "#3c4043", - "text-halo-color": "rgba(255, 255, 255, 0.9)", - "text-halo-width": 2 + "text-color": "#A82020", + "text-halo-color": "rgba(255, 255, 255, 0.95)", + "text-halo-width": 2, + "text-halo-blur": 0.5 + } + }, + { + "id": "poi-pharmacy", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 15, + "filter": ["==", "amenity", "pharmacy"], + "layout": { + "icon-image": "pharmacy", + "icon-size": ["interpolate", ["linear"], ["zoom"], + 15, 0.6, 17, 0.9 + ], + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 15, 9, 17, 11 + ], + "text-offset": [0, 1.2], + "text-anchor": "top" + }, + "paint": { + "text-color": "#187840", + "text-halo-color": "rgba(255, 255, 255, 0.95)", + "text-halo-width": 1.8, + "text-halo-blur": 0.5 + } + }, + { + "id": "poi-place-of-worship", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 14, + "filter": ["==", "amenity", "place_of_worship"], + "layout": { + "icon-image": "tourist", + "icon-size": ["interpolate", ["linear"], ["zoom"], + 14, 0.6, 17, 0.9 + ], + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 14, 9, 17, 12 + ], + "text-offset": [0, 1.2], + "text-anchor": "top" + }, + "paint": { + "text-color": "#186838", + "text-halo-color": "rgba(255, 255, 255, 0.95)", + "text-halo-width": 2, + "text-halo-blur": 0.5 + } + }, + { + "id": "poi-restaurant-cafe", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": ["in", "amenity", "restaurant", "cafe", "fast_food"], + "layout": { + "icon-image": ["match", ["get", "amenity"], "cafe", "cafe", "restaurant"], + "icon-size": ["interpolate", ["linear"], ["zoom"], + 16, 0.6, 18, 0.9 + ], + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 16, 9, 18, 11 + ], + "text-offset": [0, 1.2], + "text-anchor": "top" + }, + "paint": { + "text-color": "#403830", + "text-halo-color": "rgba(255, 255, 255, 0.92)", + "text-halo-width": 1.8, + "text-halo-blur": 0.5 + } + }, + { + "id": "poi-school", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 14, + "filter": ["in", "amenity", "school", "university", "college"], + "layout": { + "icon-image": "college", + "icon-size": ["interpolate", ["linear"], ["zoom"], + 14, 0.6, 17, 0.9 + ], + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 14, 9, 17, 12 + ], + "text-offset": [0, 1.2], + "text-anchor": "top" + }, + "paint": { + "text-color": "#4A3880", + "text-halo-color": "rgba(255, 255, 255, 0.95)", + "text-halo-width": 2, + "text-halo-blur": 0.5 + } + }, + { + "id": "poi-transit-station", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 12, + "filter": [ + "any", + ["==", "railway", "station"], + ["==", "railway", "halt"], + ["==", "railway", "tram_stop"], + ["==", "station", "subway"], + ["==", "amenity", "bus_station"] + ], + "layout": { + "icon-image": "rail", + "icon-size": ["interpolate", ["linear"], ["zoom"], + 12, 0.7, 16, 1.1 + ], + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 12, 9, 16, 12 + ], + "text-offset": [0, 1.4], + "text-anchor": "top", + "text-allow-overlap": false + }, + "paint": { + "text-color": "#A01828", + "text-halo-color": "rgba(255, 255, 255, 0.95)", + "text-halo-width": 2, + "text-halo-blur": 0.5 + } + }, + + { + "id": "place-labels-area", + "type": "symbol", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 10, + "filter": ["has", "name"], + "layout": { + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 10, 9, 14, 12, 17, 14 + ], + "text-padding": 8, + "text-allow-overlap": false, + "text-ignore-placement": false + }, + "paint": { + "text-color": "#342C20", + "text-halo-color": "rgba(242, 237, 228, 0.9)", + "text-halo-width": 2, + "text-halo-blur": 0.5 + } + }, + { + "id": "place-labels-point", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 10, + "filter": [ + "any", + ["in", "place", "city", "town", "village", "suburb", "neighbourhood", "hamlet", "locality", "quarter"], + ["in", "natural", "peak", "spring"] + ], + "layout": { + "text-field": ["coalesce", ["get", "name:ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": [ + "interpolate", ["linear"], ["zoom"], + 10, ["match", ["get", "place"], "city", 15, "town", 13, 10], + 14, ["match", ["get", "place"], "city", 19, "town", 15, 12], + 17, 14 + ], + "text-letter-spacing": [ + "match", ["get", "place"], + "city", 0.08, + "town", 0.05, + 0.02 + ], + "text-anchor": "center", + "text-padding": 10, + "text-allow-overlap": false + }, + "paint": { + "text-color": [ + "match", ["get", "place"], + "city", "#18100A", + "town", "#28201A", + "village", "#38302A", + "suburb", "#483830", + "neighbourhood", "#504038", + "#584840" + ], + "text-halo-color": "rgba(242, 237, 228, 0.94)", + "text-halo-width": [ + "match", ["get", "place"], + "city", 3, + "town", 2.5, + 2 + ], + "text-halo-blur": 0.5 + } + }, + + { + "id": "places-egypt-labels", + "type": "symbol", + "source": "places_egypt", + "source-layer": "places_egypt", + "minzoom": 12, + "layout": { + "text-field": ["coalesce", ["get", "name_ar"], ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], + 12, 9, 16, 13, 18, 15 + ], + "text-offset": [0, 1.5], + "text-anchor": "top", + "text-padding": 8, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#282018", + "text-halo-color": "rgba(255, 255, 255, 0.92)", + "text-halo-width": 2, + "text-halo-blur": 0.5 } } + ] -} +} \ No newline at end of file diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index b29adf4..4fb15c7 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -18,6 +18,25 @@ function App() { const [showResults, setShowResults] = useState(false); const [newPlace, setNewPlace] = useState<{lat: number, lng: number} | null>(null); const [placeForm, setPlaceForm] = useState({ name: '', name_ar: '', category: '' }); + const [currentRegion, setCurrentRegion] = useState('Jordan'); + + const regions = [ + { name: 'Syria', name_ar: 'سوريا', center: [36.29, 33.51], zoom: 12, flag: '🇸🇾' }, + { name: 'Jordan', name_ar: 'الأردن', center: [35.91, 31.95], zoom: 12, flag: '🇯🇴' }, + { name: 'Egypt', name_ar: 'مصر', center: [31.23, 30.04], zoom: 11, flag: '🇪🇬' }, + ]; + + const handleRegionSwitch = (region: any) => { + setCurrentRegion(region.name); + if (map) { + map.flyTo({ + center: region.center, + zoom: region.zoom, + essential: true, + duration: 3000 + }); + } + }; const handleMapClick = (lat: number, lng: number) => { setNewPlace({ lat, lng }); @@ -146,9 +165,37 @@ function App() { return (
-

Jordan Maps SaaS

+

{currentRegion} Maps SaaS

Self-Hosted Mobility Prototype

+
+ {regions.map(r => ( + + ))} +
+
diff --git a/docker-compose.yml b/docker-compose.yml index 25aea1d..e47031e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,7 +46,7 @@ services: volumes: - ./infrastructure/osm-data:/data - ./infrastructure/docker/graphhopper/config.yml:/graphhopper/config.yml - command: ["-i", "/data/region.osm.pbf", "-c", "config.yml"] + command: ["-i", "/data/mena_full.osm.pbf", "-c", "config.yml"] healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"] interval: 30s diff --git a/infrastructure/docker/martin/style.json b/infrastructure/docker/martin/style.json index cda1ae6..13c17f3 100644 --- a/infrastructure/docker/martin/style.json +++ b/infrastructure/docker/martin/style.json @@ -1,8 +1,15 @@ { "version": 8, - "name": "Intaleq Modern Premium", - "metadata": {}, - "center": [36.276008, 33.513685], + "name": "Intaleq Premium Map Style", + "metadata": { + "brand": "Intaleq", + "version": "2.0.0", + "description": "Google + OSM hybrid style with 3D buildings, railways, subway, waterways, and Intaleq brand palette" + }, + "center": [ + 36.276008, + 33.513685 + ], "zoom": 15, "glyphs": "https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf", "sprite": "https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite", @@ -13,7 +20,7 @@ "https://tiles.intaleqapp.com/planet_osm_polygon/{z}/{x}/{y}" ], "maxzoom": 14, - "attribution": "© Intaleq Mapping Solutions" + "attribution": "© Intaleq | © OpenStreetMap contributors" }, "local-osm-lines": { "type": "vector", @@ -28,6 +35,13 @@ "https://tiles.intaleqapp.com/planet_osm_point/{z}/{x}/{y}" ], "maxzoom": 14 + }, + "imported-pois": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/places_egypt/{z}/{x}/{y}" + ], + "maxzoom": 14 } }, "layers": [ @@ -35,23 +49,83 @@ "id": "background", "type": "background", "paint": { - "background-color": "#f8f9fa" + "background-color": "#EEF2F7" } }, { - "id": "water-layer", + "id": "landuse-residential", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "residential" + ], + "paint": { + "fill-color": "#F0F4F8", + "fill-opacity": 1 + } + }, + { + "id": "landuse-commercial", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "commercial" + ], + "paint": { + "fill-color": "#FAF5EE", + "fill-opacity": 1 + } + }, + { + "id": "landuse-industrial", "type": "fill", "source": "local-osm-polygons", "source-layer": "planet_osm_polygon", "filter": [ "in", - "natural", - "water", - "lake", - "riverbank" + "landuse", + "industrial", + "railway" ], "paint": { - "fill-color": "#a3ccff" + "fill-color": "#E4E8EE", + "fill-opacity": 1 + } + }, + { + "id": "landuse-cemetery", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "cemetery" + ], + "paint": { + "fill-color": "#B8D4BA", + "fill-opacity": 0.9 + } + }, + { + "id": "landuse-military", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "military" + ], + "paint": { + "fill-color": "#E2D9CC", + "fill-opacity": 0.8 } }, { @@ -60,67 +134,470 @@ "source": "local-osm-polygons", "source-layer": "planet_osm_polygon", "filter": [ - "in", - "leisure", - "park", - "garden", - "nature_reserve", - "pitch" - ], - "paint": { - "fill-color": "#dcedc8" - } - }, - { - "id": "landuse-layer", - "type": "fill", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "filter": [ - "in", - "landuse", - "residential", - "commercial", - "industrial", - "cemetery" + "any", + [ + "in", + "leisure", + "park", + "garden", + "nature_reserve", + "pitch", + "playground" + ], + [ + "in", + "landuse", + "grass", + "meadow", + "forest" + ], + [ + "in", + "natural", + "wood", + "scrub", + "heath" + ] ], "paint": { "fill-color": [ "match", [ "get", - "landuse" + "leisure" ], - "residential", - "#f1f3f4", - "commercial", - "#f8f9fa", - "industrial", - "#f1f3f4", - "cemetery", - "#dcedc8", - "#f1f3f4" + "pitch", + "#9ED4A0", + "playground", + "#B8E6B8", + "#C5E8C5" + ], + "fill-opacity": 0.85 + } + }, + { + "id": "park-outline", + "type": "line", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "leisure", + "park", + "garden", + "nature_reserve" + ], + "paint": { + "line-color": "#94D4A0", + "line-width": 0.8, + "line-opacity": 0.7 + } + }, + { + "id": "water-polygon", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "in", + "natural", + "water", + "lake", + "bay" + ], + [ + "==", + "waterway", + "riverbank" + ], + [ + "in", + "landuse", + "basin", + "reservoir" + ], + [ + "==", + "amenity", + "fountain" + ] + ], + "paint": { + "fill-color": "#9ECFE8", + "fill-opacity": 0.95 + } + }, + { + "id": "water-polygon-outline", + "type": "line", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "in", + "natural", + "water", + "lake", + "bay" + ], + [ + "==", + "waterway", + "riverbank" + ], + [ + "in", + "landuse", + "basin", + "reservoir" + ] + ], + "paint": { + "line-color": "#6BB8D8", + "line-width": 0.8, + "line-opacity": 0.8 + } + }, + { + "id": "waterway-river", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "waterway", + "river", + "canal" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "!=", + "seasonal", + "yes" + ], + [ + "!=", + "tunnel", + "yes" + ] + ], + "paint": { + "line-color": "#6BB8D8", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1.5, + 14, + 4, + 16, + 7 + ], + "line-opacity": 0.95 + } + }, + { + "id": "waterway-stream-drain", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "waterway", + "stream", + "drain", + "ditch" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "!=", + "seasonal", + "yes" + ], + [ + "!=", + "tunnel", + "yes" + ] + ], + "minzoom": 13, + "paint": { + "line-color": "#7FBFD8", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 13, + 0.8, + 16, + 2.5 + ], + "line-opacity": 0.85 + } + }, + { + "id": "railway-area", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "railway" + ], + "paint": { + "fill-color": "#DDE2EA", + "fill-opacity": 0.9 + } + }, + { + "id": "railway-rail-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "railway", + "rail", + "narrow_gauge", + "preserved" + ], + [ + "!=", + "service", + "yard" + ], + [ + "!=", + "service", + "siding" + ] + ], + "minzoom": 8, + "paint": { + "line-color": "#B0B8C5", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 8, + 2, + 12, + 4, + 16, + 8 ] } }, { - "id": "building-3d", - "type": "fill-extrusion", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "minzoom": 15, + "id": "railway-rail-core", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", "filter": [ - "has", - "building" + "all", + [ + "in", + "railway", + "rail", + "narrow_gauge", + "preserved" + ], + [ + "!=", + "service", + "yard" + ], + [ + "!=", + "service", + "siding" + ] ], - "layout": { - "visibility": "visible" - }, + "minzoom": 8, "paint": { - "fill-extrusion-color": "#e8eaed", - "fill-extrusion-height": 20, - "fill-extrusion-base": 0, - "fill-extrusion-opacity": 0.8 + "line-color": "#6B7A8E", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 8, + 1, + 12, + 2.5, + 16, + 5 + ], + "line-dasharray": [ + 6, + 4 + ] + } + }, + { + "id": "railway-subway-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "railway", + "subway", + "light_rail", + "tram", + "monorail" + ], + "minzoom": 10, + "paint": { + "line-color": [ + "match", + [ + "get", + "railway" + ], + "subway", + "#CC2233", + "light_rail", + "#0066CC", + "tram", + "#8833BB", + "monorail", + "#008855", + "#BB3344" + ], + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 3, + 14, + 6, + 16, + 10 + ] + } + }, + { + "id": "railway-subway-core", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "railway", + "subway", + "light_rail", + "tram", + "monorail" + ], + "minzoom": 10, + "paint": { + "line-color": [ + "match", + [ + "get", + "railway" + ], + "subway", + "#FF3347", + "light_rail", + "#2288FF", + "tram", + "#AA44EE", + "monorail", + "#00BB66", + "#FF4455" + ], + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1.5, + 14, + 3.5, + 16, + 6 + ] + } + }, + { + "id": "road-casing-track-path", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "track", + "path", + "footway", + "cycleway", + "steps" + ], + "minzoom": 14, + "paint": { + "line-color": "#C8CDD6", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 14, + 1, + 16, + 4 + ], + "line-dasharray": [ + 4, + 3 + ] } }, { @@ -135,12 +612,10 @@ "service", "unclassified", "living_street", - "pedestrian", - "path", - "track" + "pedestrian" ], "paint": { - "line-color": "#d4d4d4", + "line-color": "#C8D0DA", "line-width": [ "interpolate", [ @@ -149,10 +624,10 @@ [ "zoom" ], - 13, - 1, + 12, + 1.5, 16, - 8 + 10 ] } }, @@ -168,12 +643,10 @@ "service", "unclassified", "living_street", - "pedestrian", - "path", - "track" + "pedestrian" ], "paint": { - "line-color": "#ffffff", + "line-color": "#FFFFFF", "line-width": [ "interpolate", [ @@ -182,10 +655,10 @@ [ "zoom" ], - 13, - 0.5, + 12, + 0.8, 16, - 6 + 8 ] } }, @@ -201,7 +674,7 @@ "tertiary_link" ], "paint": { - "line-color": "#e0e0e0", + "line-color": "#BCC5D2", "line-width": [ "interpolate", [ @@ -210,10 +683,10 @@ [ "zoom" ], - 12, - 1.5, + 11, + 2, 16, - 12 + 14 ] } }, @@ -229,7 +702,7 @@ "tertiary_link" ], "paint": { - "line-color": "#ffffff", + "line-color": "#FFFFFF", "line-width": [ "interpolate", [ @@ -238,10 +711,10 @@ [ "zoom" ], - 12, - 1, + 11, + 1.2, 16, - 9 + 11 ] } }, @@ -257,7 +730,7 @@ "secondary_link" ], "paint": { - "line-color": "#cfd8dc", + "line-color": "#B8D4EC", "line-width": [ "interpolate", [ @@ -266,10 +739,10 @@ [ "zoom" ], - 12, - 2, + 11, + 2.5, 16, - 14 + 16 ] } }, @@ -285,7 +758,7 @@ "secondary_link" ], "paint": { - "line-color": "#f1f5f9", + "line-color": "#F0F7FF", "line-width": [ "interpolate", [ @@ -294,10 +767,10 @@ [ "zoom" ], - 12, - 1.5, + 11, + 1.8, 16, - 11 + 13 ] } }, @@ -313,7 +786,7 @@ "primary_link" ], "paint": { - "line-color": "#facc15", + "line-color": "#E8C96A", "line-width": [ "interpolate", [ @@ -322,10 +795,10 @@ [ "zoom" ], - 12, + 10, 3, 16, - 16 + 18 ] } }, @@ -341,7 +814,7 @@ "primary_link" ], "paint": { - "line-color": "#fefce8", + "line-color": "#FFC72C", "line-width": [ "interpolate", [ @@ -350,10 +823,10 @@ [ "zoom" ], - 12, + 10, 2, 16, - 12 + 14 ] } }, @@ -371,7 +844,7 @@ "trunk_link" ], "paint": { - "line-color": "#fb923c", + "line-color": "#F4A261", "line-width": [ "interpolate", [ @@ -380,10 +853,10 @@ [ "zoom" ], - 12, + 9, 4, 16, - 18 + 20 ] } }, @@ -401,7 +874,7 @@ "trunk_link" ], "paint": { - "line-color": "#ffedd5", + "line-color": "#E76F2A", "line-width": [ "interpolate", [ @@ -410,45 +883,331 @@ [ "zoom" ], - 12, + 9, 2.5, 16, - 14 + 16 ] } }, { - "id": "road-labels", + "id": "building-fill-flat", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "has", + "building" + ], + "maxzoom": 15, + "paint": { + "fill-color": "#DDD8D0", + "fill-opacity": 0.9, + "fill-outline-color": "#C8C2B8" + } + }, + { + "id": "building-3d", + "type": "fill-extrusion", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 15, + "filter": [ + "has", + "building" + ], + "paint": { + "fill-extrusion-color": [ + "match", + [ + "get", + "building" + ], + "commercial", + "#E0D8CC", + "retail", + "#E8DDD0", + "industrial", + "#D8DDE4", + "church", + "#E4DCF0", + "mosque", + "#D4EAD8", + "hospital", + "#F0E0E0", + "school", + "#E8ECD4", + "university", + "#E4E8D4", + "hotel", + "#E0E4EE", + "apartments", + "#E8E4DC", + "#E4DFDA" + ], + "fill-extrusion-height": [ + "coalesce", + [ + "to-number", + [ + "get", + "height" + ], + 0 + ], + [ + "*", + [ + "to-number", + [ + "get", + "building:levels" + ], + 3 + ], + 3.5 + ], + 12 + ], + "fill-extrusion-base": [ + "coalesce", + [ + "to-number", + [ + "get", + "min_height" + ], + 0 + ], + 0 + ], + "fill-extrusion-opacity": 0.85, + "fill-extrusion-vertical-gradient": true + } + }, + { + "id": "railway-label", "type": "symbol", "source": "local-osm-lines", "source-layer": "planet_osm_line", - "minzoom": 15, + "filter": [ + "in", + "railway", + "rail", + "subway", + "light_rail", + "tram" + ], + "minzoom": 13, "layout": { - "text-field": "{name}", + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], "text-font": [ "Noto Sans Regular" ], - "text-size": 13, + "text-size": 10, "symbol-placement": "line", - "text-letter-spacing": 0.05, - "text-padding": 5, - "text-allow-overlap": false, - "text-ignore-placement": false + "text-padding": 6, + "text-allow-overlap": false }, "paint": { - "text-color": "#3c4043", - "text-halo-color": "rgba(255, 255, 255, 0.8)", + "text-color": [ + "match", + [ + "get", + "railway" + ], + "subway", + "#CC2233", + "light_rail", + "#0055BB", + "tram", + "#7722AA", + "#4A5568" + ], + "text-halo-color": "rgba(255,255,255,0.9)", "text-halo-width": 2 } }, { - "id": "place-labels", + "id": "waterway-label", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "waterway", + "river", + "canal" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "has", + "name" + ] + ], + "minzoom": 12, + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "symbol-placement": "line", + "text-letter-spacing": 0.1 + }, + "paint": { + "text-color": "#2E86AB", + "text-halo-color": "rgba(255,255,255,0.85)", + "text-halo-width": 2 + } + }, + { + "id": "building-number-polygon", + "type": "symbol", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 17, + "filter": [ + "has", + "addr:housenumber" + ], + "layout": { + "text-field": "{addr:housenumber}", + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-allow-overlap": false, + "text-ignore-placement": false + }, + "paint": { + "text-color": "#5A5048", + "text-halo-color": "rgba(255,255,255,0.95)", + "text-halo-width": 1.5 + } + }, + { + "id": "building-number-point", "type": "symbol", "source": "local-osm-points", "source-layer": "planet_osm_point", + "minzoom": 17, + "filter": [ + "has", + "addr:housenumber" + ], + "layout": { + "text-field": "{addr:housenumber}", + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#5A5048", + "text-halo-color": "rgba(255,255,255,0.95)", + "text-halo-width": 1.5 + } + }, + { + "id": "road-labels-minor", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "residential", + "service", + "unclassified", + "living_street" + ], + "minzoom": 16, + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "symbol-placement": "line", + "text-letter-spacing": 0.04, + "text-padding": 4, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#4A5568", + "text-halo-color": "rgba(255,255,255,0.85)", + "text-halo-width": 1.5 + } + }, + { + "id": "road-labels-major", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "primary", + "secondary", + "tertiary", + "motorway", + "trunk" + ], "minzoom": 13, "layout": { - "text-field": "{name}", + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], "text-font": [ "Noto Sans Regular" ], @@ -461,22 +1220,489 @@ "zoom" ], 13, - 12, + 11, 16, - 16 + 14 ], - "text-offset": [ - 0, - 1.5 - ], - "text-anchor": "top", - "visibility": "visible" + "symbol-placement": "line", + "text-letter-spacing": 0.05, + "text-padding": 5, + "text-allow-overlap": false }, "paint": { - "text-color": "#3c4043", - "text-halo-color": "rgba(255, 255, 255, 0.9)", + "text-color": "#2D3748", + "text-halo-color": "rgba(255,255,255,0.9)", "text-halo-width": 2 } + }, + { + "id": "poi-hospital", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 13, + "filter": [ + "==", + "amenity", + "hospital" + ], + "layout": { + "icon-image": "hospital", + "icon-size": 1, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "text-offset": [ + 0, + 1.2 + ], + "text-anchor": "top", + "text-allow-overlap": false + }, + "paint": { + "text-color": "#C0392B", + "text-halo-color": "white", + "text-halo-width": 2 + } + }, + { + "id": "poi-pharmacy", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 15, + "filter": [ + "==", + "amenity", + "pharmacy" + ], + "layout": { + "icon-image": "pharmacy", + "icon-size": 0.8, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-offset": [ + 0, + 1.2 + ], + "text-anchor": "top" + }, + "paint": { + "text-color": "#1A7A3C", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-place-of-worship", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 14, + "filter": [ + "==", + "amenity", + "place_of_worship" + ], + "layout": { + "icon-image": "tourist", + "icon-size": 0.8, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "text-offset": [ + 0, + 1.2 + ], + "text-anchor": "top" + }, + "paint": { + "text-color": "#1A6B3A", + "text-halo-color": "white", + "text-halo-width": 2 + } + }, + { + "id": "poi-restaurant-cafe", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "in", + "amenity", + "restaurant", + "cafe", + "fast_food" + ], + "layout": { + "icon-image": [ + "match", + [ + "get", + "amenity" + ], + "cafe", + "cafe", + "restaurant" + ], + "icon-size": 0.8, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-offset": [ + 0, + 1.2 + ], + "text-anchor": "top" + }, + "paint": { + "text-color": "#3D4A5C", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-school", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 14, + "filter": [ + "in", + "amenity", + "school", + "university", + "college" + ], + "layout": { + "icon-image": "college", + "icon-size": 0.8, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "text-offset": [ + 0, + 1.2 + ], + "text-anchor": "top" + }, + "paint": { + "text-color": "#5A4A8A", + "text-halo-color": "white", + "text-halo-width": 2 + } + }, + { + "id": "poi-transit-station", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 12, + "filter": [ + "any", + [ + "==", + "railway", + "station" + ], + [ + "==", + "railway", + "halt" + ], + [ + "==", + "railway", + "tram_stop" + ], + [ + "==", + "station", + "subway" + ], + [ + "==", + "amenity", + "bus_station" + ] + ], + "layout": { + "icon-image": "rail", + "icon-size": 1, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "text-offset": [ + 0, + 1.4 + ], + "text-anchor": "top", + "text-allow-overlap": false + }, + "paint": { + "text-color": "#CC2233", + "text-halo-color": "rgba(255,255,255,0.95)", + "text-halo-width": 2 + } + }, + { + "id": "place-labels-area", + "type": "symbol", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 10, + "filter": [ + "has", + "name" + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 10, + 14, + 13 + ], + "text-padding": 8, + "text-allow-overlap": false, + "text-ignore-placement": false + }, + "paint": { + "text-color": "#34495E", + "text-halo-color": "rgba(255,255,255,0.85)", + "text-halo-width": 2 + } + }, + { + "id": "place-labels-point", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 10, + "filter": [ + "any", + [ + "in", + "place", + "city", + "town", + "village", + "suburb", + "neighbourhood", + "hamlet", + "locality", + "quarter" + ], + [ + "in", + "natural", + "peak", + "spring" + ] + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + [ + "match", + [ + "get", + "place" + ], + "city", + 16, + "town", + 14, + 11 + ], + 14, + [ + "match", + [ + "get", + "place" + ], + "city", + 20, + "town", + 16, + 13 + ], + 17, + 14 + ], + "text-letter-spacing": [ + "match", + [ + "get", + "place" + ], + "city", + 0.08, + "town", + 0.05, + 0.02 + ], + "text-anchor": "center", + "text-padding": 10, + "text-allow-overlap": false + }, + "paint": { + "text-color": [ + "match", + [ + "get", + "place" + ], + "city", + "#1A2740", + "town", + "#2C3E50", + "village", + "#3D4F62", + "suburb", + "#4A5568", + "neighbourhood", + "#556677", + "#607080" + ], + "text-halo-color": "rgba(255,255,255,0.92)", + "text-halo-width": [ + "match", + [ + "get", + "place" + ], + "city", + 3, + "town", + 2.5, + 2 + ] + } } ] -} +} \ No newline at end of file diff --git a/infrastructure/scripts/import-regions.sh b/infrastructure/scripts/import-regions.sh new file mode 100644 index 0000000..7509a0f --- /dev/null +++ b/infrastructure/scripts/import-regions.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Script to import Egypt and Jordan OSM data into the PostGIS database +# نص برمجي لاستيراد بيانات مصر والأردن إلى قاعدة البيانات + +set -e + +DATA_DIR="./infrastructure/osm-data" +DB_USER=${POSTGRES_USER:-mapuser} +DB_NAME=${POSTGRES_DB:-mapdb} + +echo "🌍 Starting MENA Regional Data Import..." +echo "🌍 البدء في استيراد البيانات الإقليمية..." + +# 1. Check for files +if [ ! -f "$DATA_DIR/jordan-latest.osm.pbf" ]; then + echo "❌ Jordan PBF missing. Please run setup-osm.sh first." + exit 1 +fi + +if [ ! -f "$DATA_DIR/egypt-latest.osm.pbf" ]; then + echo "📥 Downloading Egypt OSM PBF..." + curl -L https://download.geofabrik.de/africa/egypt-latest.osm.pbf -o "$DATA_DIR/egypt-latest.osm.pbf" +fi + +# 2. Detect Docker Compose Command +if command -v docker-compose &> /dev/null; then + DOKCER_COMPOSE="docker-compose" +else + DOKCER_COMPOSE="docker compose" +fi + +echo "🐳 Using $DOKCER_COMPOSE..." + +# 3. Import Jordan (Append) +echo "🇯🇴 Importing Jordan data (Append mode)..." +$DOKCER_COMPOSE --profile import run --rm osm-import osm2pgsql \ + --append --slim --cache 1000 \ + --database "$DB_NAME" --host db --user "$DB_USER" \ + /data/jordan-latest.osm.pbf + +# 4. Import Egypt (Append) +echo "🇪🇬 Importing Egypt data (Append mode)..." +$DOKCER_COMPOSE --profile import run --rm osm-import osm2pgsql \ + --append --slim --cache 1000 \ + --database "$DB_NAME" --host db --user "$DB_USER" \ + /data/egypt-latest.osm.pbf + +echo "✅ Import complete. Restarting tile and routing services..." +$DOKCER_COMPOSE restart martin routing + +echo "🚀 MENA Regional mapping is now live!" +echo "🚀 تم تفعيل خرائط المنطقة بنجاح!"