From 32865e78dfb6cbb97b809d18b3ae976155653f15 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sun, 16 Aug 2026 13:17:56 +0300 Subject: [PATCH] Backup before syncing server scripts --- .../administrative-linking.service.ts | 17 +- .../api/src/geocoding/dto/search-query.dto.ts | 4 +- .../entities/map-candidate.entity.ts | 3 +- .../geocoding/entities/place-iraq.entity.ts | 5 + .../src/geocoding/geocoding-init.service.ts | 17 +- .../api/src/geocoding/geocoding.controller.ts | 10 +- apps/api/src/geocoding/geocoding.module.ts | 2 + apps/api/src/geocoding/geocoding.service.ts | 171 +- .../src/geocoding/map-refinement.service.ts | 4 + apps/web/public/style.json | 990 +++++-- apps/web/src/components/MapComponent.tsx | 4 +- apps/web/src/pages/CompareView.tsx | 3 + apps/web/src/utils/mapIcons.ts | 67 + docker-compose.yml | 7 +- docs/ROUTING_ONEWAY_AR.md | 110 + infrastructure/scripts/build-sprite.py | 94 + infrastructure/scripts/check-bidirectional.sh | 7 +- infrastructure/scripts/overture_ingest.sh | 198 +- infrastructure/scripts/update-data.sh | 74 +- infrastructure/scripts/verify-oneway.sh | 82 + .../flutter-sdk/.dart_tool/package_graph.json | 34 +- packages/flutter-sdk/.gitignore | 5 + packages/flutter-sdk/CHANGELOG.md | 16 + packages/flutter-sdk/assets/style.json | 1669 +++++++---- packages/flutter-sdk/pubspec.yaml | 2 +- style.json | 2447 ++++++++--------- 26 files changed, 3919 insertions(+), 2123 deletions(-) create mode 100644 apps/api/src/geocoding/entities/place-iraq.entity.ts create mode 100644 apps/web/src/utils/mapIcons.ts create mode 100644 docs/ROUTING_ONEWAY_AR.md create mode 100644 infrastructure/scripts/build-sprite.py create mode 100755 infrastructure/scripts/verify-oneway.sh create mode 100644 packages/flutter-sdk/.gitignore diff --git a/apps/api/src/geocoding/administrative-linking.service.ts b/apps/api/src/geocoding/administrative-linking.service.ts index fdd140e..02dc3fa 100644 --- a/apps/api/src/geocoding/administrative-linking.service.ts +++ b/apps/api/src/geocoding/administrative-linking.service.ts @@ -63,8 +63,12 @@ export class AdministrativeLinkingService { // Determine country from BBox if not provided (simple heuristic) let detectedCountry = country; if (!detectedCountry) { - const firstLat = parseFloat(bbox.split(',')[0]); - if (firstLat > 32.3) detectedCountry = 'syria'; + const parts = bbox.split(',').map(parseFloat); + const firstLat = parts[0]; + const lngs = [parts[1], parts[3]].filter(n => !isNaN(n)); + const maxLng = lngs.length ? Math.max(...lngs) : undefined; + if (maxLng !== undefined && maxLng > 42.5) detectedCountry = 'iraq'; + else if (firstLat > 32.3) detectedCountry = 'syria'; else if (firstLat < 31.0) detectedCountry = 'egypt'; else detectedCountry = 'jordan'; } @@ -98,8 +102,13 @@ export class AdministrativeLinkingService { LIMIT 1 ), ( + -- حدّ 50كم إلزامي: بدونه تُسنَد نقطة لا تقع داخل أي حدود إلى أقرب + -- مقاطعة في الجدول كله مهما بَعُدت. حين استوردنا العراق قبل حدوده + -- الإدارية، رُبطت أحياء بغداد الـ185 بمقاطعات سورية/أردنية على بعد + -- ~700كم. عدم الربط أفضل من ربط خاطئ صامت. SELECT id FROM admin_boundaries ab WHERE ab.admin_level IN (6, 8) + AND ST_DWithin(ab.geom::geography, np.geometry::geography, 50000) ORDER BY ab.geom::geometry <-> np.geometry::geometry LIMIT 1 ) @@ -209,9 +218,9 @@ export class AdministrativeLinkingService { } /** - * Step 3: Link all Places (Jordan, Syria, Egypt) to the full administrative hierarchy + * Step 3: Link all Places (Jordan, Syria, Egypt, Iraq) to the full administrative hierarchy */ - async linkPlaces(country: 'jordan' | 'syria' | 'egypt') { + async linkPlaces(country: 'jordan' | 'syria' | 'egypt' | 'iraq') { const tableName = `places_${country}`; // Pre-flight diagnostics diff --git a/apps/api/src/geocoding/dto/search-query.dto.ts b/apps/api/src/geocoding/dto/search-query.dto.ts index 4332a43..40e3067 100644 --- a/apps/api/src/geocoding/dto/search-query.dto.ts +++ b/apps/api/src/geocoding/dto/search-query.dto.ts @@ -28,7 +28,7 @@ export class SearchQueryDto { radius?: number = 20000; @IsOptional() - @IsEnum(['jordan', 'syria', 'egypt']) - @ApiPropertyOptional({ description: 'Country filter', enum: ['jordan', 'syria', 'egypt'] }) + @IsEnum(['jordan', 'syria', 'egypt', 'iraq']) + @ApiPropertyOptional({ description: 'Country filter', enum: ['jordan', 'syria', 'egypt', 'iraq'] }) country?: string; } diff --git a/apps/api/src/geocoding/entities/map-candidate.entity.ts b/apps/api/src/geocoding/entities/map-candidate.entity.ts index 98ea9e2..8513a1e 100644 --- a/apps/api/src/geocoding/entities/map-candidate.entity.ts +++ b/apps/api/src/geocoding/entities/map-candidate.entity.ts @@ -10,7 +10,8 @@ export enum CandidateStatus { export enum CountryCode { JORDAN = 'JORDAN', SYRIA = 'SYRIA', - EGYPT = 'EGYPT' + EGYPT = 'EGYPT', + IRAQ = 'IRAQ' } @Entity('map_candidates') diff --git a/apps/api/src/geocoding/entities/place-iraq.entity.ts b/apps/api/src/geocoding/entities/place-iraq.entity.ts new file mode 100644 index 0000000..d23458f --- /dev/null +++ b/apps/api/src/geocoding/entities/place-iraq.entity.ts @@ -0,0 +1,5 @@ +import { Entity } from 'typeorm'; +import { BasePlace } from './base-place.entity'; + +@Entity('places_iraq') +export class PlaceIraq extends BasePlace {} diff --git a/apps/api/src/geocoding/geocoding-init.service.ts b/apps/api/src/geocoding/geocoding-init.service.ts index 1c89eb9..3d07de5 100644 --- a/apps/api/src/geocoding/geocoding-init.service.ts +++ b/apps/api/src/geocoding/geocoding-init.service.ts @@ -20,7 +20,7 @@ export class GeocodingInitService implements OnModuleInit { await this.repo.query('CREATE EXTENSION IF NOT EXISTS pg_trgm;'); // Migration: Rename old admin_level columns to descriptive names if they exist - const tables = ['places_jordan', 'places_syria', 'places_egypt']; + const tables = ['places_jordan', 'places_syria', 'places_egypt', 'places_iraq']; const columnMapping = [ { old: 'admin_level4_id', new: 'governorate_id' }, { old: 'admin_level6_id', new: 'district_id' }, @@ -83,15 +83,24 @@ export class GeocodingInitService implements OnModuleInit { FOR EACH ROW EXECUTE FUNCTION sync_place_location(); `); - // 4. GIST Geometry Indexes for Jordan and Egypt + await this.repo.query(` + DROP TRIGGER IF EXISTS trg_sync_place_location_iraq ON places_iraq; + CREATE TRIGGER trg_sync_place_location_iraq + BEFORE INSERT OR UPDATE ON places_iraq + FOR EACH ROW EXECUTE FUNCTION sync_place_location(); + `); + + // 4. GIST Geometry Indexes for Jordan, Egypt and Iraq await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_jordan_location ON places_jordan USING gist (location);'); await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_egypt_location ON places_egypt USING gist (location);'); + await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_iraq_location ON places_iraq USING gist (location);'); - // 5. GIST Trigram Indexes for Jordan and Egypt + // 5. GIST Trigram Indexes for Jordan, Egypt and Iraq await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_jordan_names_trgm ON places_jordan USING gist (name_ar gist_trgm_ops, name_en gist_trgm_ops);'); await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_egypt_names_trgm ON places_egypt USING gist (name_ar gist_trgm_ops, name_en gist_trgm_ops);'); + await this.repo.query('CREATE INDEX IF NOT EXISTS idx_places_iraq_names_trgm ON places_iraq USING gist (name_ar gist_trgm_ops, name_en gist_trgm_ops);'); - this.logger.log('Geocoding database triggers and optimized indexes initialized for Syria, Jordan, and Egypt.'); + this.logger.log('Geocoding database triggers and optimized indexes initialized for Syria, Jordan, Egypt, and Iraq.'); } catch (err) { this.logger.error('Failed to initialize database geocoding triggers:', err); } diff --git a/apps/api/src/geocoding/geocoding.controller.ts b/apps/api/src/geocoding/geocoding.controller.ts index cee4a9f..14d23f4 100644 --- a/apps/api/src/geocoding/geocoding.controller.ts +++ b/apps/api/src/geocoding/geocoding.controller.ts @@ -61,7 +61,7 @@ export class GeocodingController { @ApiOperation({ summary: 'Delete a place by name or ID' }) @ApiQuery({ name: 'name', required: false }) @ApiQuery({ name: 'id', required: false, type: Number }) - @ApiQuery({ name: 'country', required: true, enum: ['jordan', 'syria', 'egypt'] }) + @ApiQuery({ name: 'country', required: true, enum: ['jordan', 'syria', 'egypt', 'iraq'] }) async deletePlace( @Query('country') country: string, @Query('name') name?: string, @@ -124,7 +124,7 @@ export class GeocodingController { @UseGuards(AdminGuard) @ApiOperation({ summary: 'Sync neighborhood points from OSM for a bbox' }) @ApiQuery({ name: 'bbox', required: false }) - @ApiQuery({ name: 'country', required: false, enum: ['jordan', 'syria', 'egypt'] }) + @ApiQuery({ name: 'country', required: false, enum: ['jordan', 'syria', 'egypt', 'iraq'] }) async syncNeighborhoods(@Query('bbox') bbox?: string, @Query('country') country?: string) { return this.adminLinkingService.syncOsmNeighborhoodPoints(bbox, country); } @@ -132,7 +132,7 @@ export class GeocodingController { @Post('admin/generate-voronoi') @UseGuards(AdminGuard) @ApiOperation({ summary: 'Generate Voronoi polygons for neighborhoods' }) - @ApiQuery({ name: 'country', required: false, enum: ['jordan', 'syria', 'egypt'] }) + @ApiQuery({ name: 'country', required: false, enum: ['jordan', 'syria', 'egypt', 'iraq'] }) async generateVoronoi(@Query('country') country?: string) { return this.adminLinkingService.generateVoronoiNeighborhoods(country); } @@ -140,8 +140,8 @@ export class GeocodingController { @Post('admin/link-places') @UseGuards(AdminGuard) @ApiOperation({ summary: 'Link places to administrative hierarchy' }) - @ApiQuery({ name: 'country', required: true, enum: ['jordan', 'syria', 'egypt'] }) - async linkPlaces(@Query('country') country: 'jordan' | 'syria' | 'egypt') { + @ApiQuery({ name: 'country', required: true, enum: ['jordan', 'syria', 'egypt', 'iraq'] }) + async linkPlaces(@Query('country') country: 'jordan' | 'syria' | 'egypt' | 'iraq') { return this.adminLinkingService.linkPlaces(country); } } diff --git a/apps/api/src/geocoding/geocoding.module.ts b/apps/api/src/geocoding/geocoding.module.ts index 21913aa..4e35ca1 100644 --- a/apps/api/src/geocoding/geocoding.module.ts +++ b/apps/api/src/geocoding/geocoding.module.ts @@ -6,6 +6,7 @@ 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 { PlaceIraq } from './entities/place-iraq.entity'; import { OsmArea } from './entities/osm-area.entity'; import { OsmPointWithArea } from './entities/osm-point-with-area.entity'; import { AdminBoundary } from './entities/admin-boundary.entity'; @@ -27,6 +28,7 @@ import { IndexRefreshService } from './index-refresh.service'; PlaceSyria, PlaceJordan, PlaceEgypt, + PlaceIraq, OsmArea, OsmPointWithArea, AdminBoundary, diff --git a/apps/api/src/geocoding/geocoding.service.ts b/apps/api/src/geocoding/geocoding.service.ts index 2a3350d..d5235e8 100644 --- a/apps/api/src/geocoding/geocoding.service.ts +++ b/apps/api/src/geocoding/geocoding.service.ts @@ -6,6 +6,7 @@ import type { Cache } from 'cache-manager'; import { PlaceSyria } from './entities/place-syria.entity'; import { PlaceJordan } from './entities/place-jordan.entity'; import { PlaceEgypt } from './entities/place-egypt.entity'; +import { PlaceIraq } from './entities/place-iraq.entity'; import { BasePlace } from './entities/base-place.entity'; import { OsmArea } from './entities/osm-area.entity'; import { OsmPointWithArea } from './entities/osm-point-with-area.entity'; @@ -23,6 +24,8 @@ export class GeocodingService { private placesJordanRepository: Repository, @InjectRepository(PlaceEgypt) private placesEgyptRepository: Repository, + @InjectRepository(PlaceIraq) + private placesIraqRepository: Repository, @InjectRepository(OsmArea) private osmAreasRepository: Repository, @InjectRepository(OsmPointWithArea) @@ -35,6 +38,11 @@ export class GeocodingService { * تحديد المستودع المناسب بناءً على الإحداثيات الجغرافية */ private getRepositoryForCoords(lat: number, lng: number): Repository { + // Iraq checked first: its western desert (Anbar) extends to lng ~38.7, which + // overlaps the Jordan/Syria box below (lng <= 42.5). Order matters here. + if (lat >= 29 && lat <= 37.5 && lng >= 38.7 && lng <= 48.8) { + return (this.placesIraqRepository as unknown) as Repository; + } if (lat >= 29 && lat <= 37.5 && lng >= 34.5 && lng <= 42.5) { if (lat > 32.5 && lng > 35.8) return (this.placesSyriaRepository as unknown) as Repository; return (this.placesJordanRepository as unknown) as Repository; @@ -47,6 +55,9 @@ export class GeocodingService { private identifyRegion(lat?: number, lng?: number): string | undefined { if (lat === undefined || lng === undefined) return undefined; + if (lat >= 29 && lat <= 37.5 && lng >= 38.7 && lng <= 48.8) { + return 'iraq'; + } if (lat >= 29 && lat <= 37.5 && lng >= 34.5 && lng <= 42.5) { if (lat > 32.5 && lng > 35.8) return 'syria'; return 'jordan'; @@ -60,6 +71,7 @@ export class GeocodingService { private getTableNameForRepo(repo: Repository): string { if (repo === (this.placesJordanRepository as unknown)) return 'places_jordan'; if (repo === (this.placesEgyptRepository as unknown)) return 'places_egypt'; + if (repo === (this.placesIraqRepository as unknown)) return 'places_iraq'; return 'places_syria'; } @@ -98,7 +110,7 @@ export class GeocodingService { } let regionCondition = ''; - if (targetRegion && ['syria', 'jordan', 'egypt'].includes(targetRegion)) { + if (targetRegion && ['syria', 'jordan', 'egypt', 'iraq'].includes(targetRegion)) { regionCondition = `AND (region = '${targetRegion}' OR region = 'global')`; } @@ -251,6 +263,7 @@ export class GeocodingService { private getRepoByTableName(tableName: string): Repository { if (tableName === 'places_egypt') return this.placesEgyptRepository; + if (tableName === 'places_iraq') return this.placesIraqRepository; if (tableName === 'places_jordan') return this.placesJordanRepository; return this.placesSyriaRepository; } @@ -296,7 +309,7 @@ export class GeocodingService { let queryParams: any[] = [`${normalizedQuery}%`]; let regionCondition = ''; - if (targetRegion && ['syria', 'jordan', 'egypt'].includes(targetRegion)) { + if (targetRegion && ['syria', 'jordan', 'egypt', 'iraq'].includes(targetRegion)) { regionCondition = `AND (region = '${targetRegion}' OR region = 'global')`; } @@ -336,10 +349,62 @@ export class GeocodingService { } } + /** + * العنوان العراقي بنية رقمية: محافظة ← منطقة ← محلة ← زقاق ← دار. + * المحلة والزقاق هما ما يستعمله الناس فعلياً، لا اسم الشارع. + * + * في OSM يُخزَّن الزقاق كاسم الطريق نفسه بصيغة "647-19" (محلة-زقاق) داخل + * planet_osm_line — وهو الجدول الوحيد الذي يحملها، ولم يكن reverseGeocode + * يستعلمه إطلاقاً. لذا كان السائق يقف داخل زقاق 19 ولا نملك ما نسمّيه به. + * + * هندسة planet_osm_line بـ SRID 3857 (افتراضي osm2pgsql بلا -E)، لذا نحوّل + * نقطة البحث إليها ليعمل فهرس GIST؛ ثم نقيس المسافة الحقيقية على geography + * للفائز وحده — فمسافات 3857 منتفخة بنحو 19% عند خط عرض بغداد. + */ + private async findIraqiAddress(lat: number, lng: number) { + const MAX_DISTANCE_M = 150; // أبعد من ذلك لم يعد الزقاق وصفاً للموقع + try { + const rows = await this.osmPointsRepository.query( + ` + WITH p AS ( + SELECT ST_Transform(ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326), 3857) AS g3857, + ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)::geography AS geog + ) + SELECT l.name, + ST_Distance(ST_Transform(l.way, 4326)::geography, p.geog) AS distance + FROM planet_osm_line l, p + WHERE l.name ~ '^[0-9]{2,4}-[0-9]{1,3}$' + ORDER BY l.way <-> p.g3857 + LIMIT 1 + `, + [lng, lat], + ); + + const row = rows?.[0]; + if (!row || Number(row.distance) > MAX_DISTANCE_M) return null; + + const [mahalla, zuqaq] = String(row.name).split('-'); + return { + mahalla, + zuqaq, + distance: Number(row.distance), + text: `محلة ${mahalla}، زقاق ${zuqaq}`, + }; + } catch (e) { + this.logger.warn(`Iraqi address lookup failed: ${e.message}`); + return null; + } + } + async reverseGeocode(lat: number, lng: number) { try { const repo = this.getRepositoryForCoords(lat, lng); const tableName = this.getTableNameForRepo(repo); + // نطلقه بالتوازي مع بقية الاستعلامات؛ العراق وحده يدفع تكلفته + const iraqiAddressPromise = + this.identifyRegion(lat, lng) === 'iraq' + ? this.findIraqiAddress(lat, lng) + : Promise.resolve(null); const queryPromises: Promise[] = []; queryPromises.push(repo.query(` @@ -402,7 +467,10 @@ export class GeocodingService { ORDER BY location <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 5 `, [lng, lat])); - const results = await Promise.allSettled(queryPromises); + const [results, iraqiAddress] = await Promise.all([ + Promise.allSettled(queryPromises), + iraqiAddressPromise, + ]); let allResults: any[] = []; results.forEach(res => { if (res.status === 'fulfilled' && res.value) allResults.push(...res.value); @@ -413,7 +481,17 @@ export class GeocodingService { .slice(0, 5) .map(r => { const distance = Number(r.distance); - const fullAddressParts = [r.name_ar || r.name, r.address, r.neighbourhood, r.district, r.governorate].filter(Boolean); + const ZUQAQ_NAME_RE = /^[0-9]{2,4}-[0-9]{1,3}$/; + const rawName = r.name_ar || r.name; + const nameIsZuqaq = typeof rawName === 'string' && ZUQAQ_NAME_RE.test(rawName); + const fullAddressParts = [ + nameIsZuqaq ? null : rawName, // "605-9" كنتيجة بحث لا يضيف شيئاً فوق "زقاق 9" + r.address, + iraqiAddress?.text, + r.neighbourhood, + r.district, + r.governorate, + ].filter(Boolean); let humanReadable = r.name_ar || r.name; if (distance <= 20) { @@ -430,8 +508,18 @@ export class GeocodingService { humanReadable = [streetPart, districtPart].filter(Boolean).join('، '); } + // في العراق المحلة والزقاق هما العنوان الفعلي، فيتصدّران الوصف + // ويبقى المعلم القريب لاحقةً توضيحية — إلا إذا كان "المعلم" نفسه + // مجرد اسم زقاق آخر (مثل "605-9")، فتكرار الرقم لا يفيد أحداً. + if (iraqiAddress) { + const landmark = distance <= 70 && !nameIsZuqaq ? rawName : null; + humanReadable = [iraqiAddress.text, landmark].filter(Boolean).join(' — '); + } + return { ...r, + mahalla: iraqiAddress?.mahalla, + zuqaq: iraqiAddress?.zuqaq, latitude: parseFloat(r.latitude), longitude: parseFloat(r.longitude), human_readable_address: humanReadable, @@ -444,20 +532,50 @@ export class GeocodingService { } } + /** + * يقبل lat/lng أو latitude/longitude ويرفض ما ليس رقماً. + * + * سبب وجوده: العملاء يرسلون {lat, lng} (كما في POST /geocoding/places)، بينما + * addPlace كان يقرأ data.latitude فقط → Number(undefined) = NaN. عندها تفشل كل + * مقارنات getRepositoryForCoords فيسقط الاستدعاء على مستودع سوريا الافتراضي، + * فيظهر مكان أردني في دمشق بإحداثيات تالفة. الصمت هنا أسوأ من الخطأ. + */ + private extractCoords(data: any): { lat: number; lng: number } { + const lat = Number(data?.lat ?? data?.latitude); + const lng = Number(data?.lng ?? data?.longitude); + if (!Number.isFinite(lat) || !Number.isFinite(lng)) { + throw new HttpException( + 'Invalid coordinates: provide numeric lat/lng (or latitude/longitude)', + HttpStatus.BAD_REQUEST, + ); + } + if (lat < -90 || lat > 90 || lng < -180 || lng > 180) { + throw new HttpException( + `Coordinates out of range: lat=${lat}, lng=${lng}`, + HttpStatus.BAD_REQUEST, + ); + } + return { lat, lng }; + } + async addPlace(data: Partial) { try { - const lat = Number(data.latitude), lng = Number(data.longitude); + const { lat, lng } = this.extractCoords(data); const repo = this.getRepositoryForCoords(lat, lng), tableName = this.getTableNameForRepo(repo); const newPlace = repo.create({ ...data, latitude: lat, longitude: lng, created_at: new Date(), location: { type: 'Point', coordinates: [lng, lat] } }); const savedPlace = await repo.save(newPlace); await repo.query(`UPDATE ${tableName} SET location = ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) WHERE id = $3`, [lng, lat, savedPlace.id]); return { ...savedPlace, latitude: lat, longitude: lng, location: `POINT(${lng} ${lat})` }; - } catch (error) { throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); } + } catch (error) { + // لا نبتلع أخطاء التحقق (400) ونحولها إلى 500 + if (error instanceof HttpException) throw error; + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } } async upsertPlace(data: Partial) { try { - const lat = Number(data.latitude), lng = Number(data.longitude); + const { lat, lng } = this.extractCoords(data); const repo = this.getRepositoryForCoords(lat, lng), tableName = this.getTableNameForRepo(repo); const existing = await repo.query(`SELECT id, name FROM ${tableName} WHERE ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) < 15 LIMIT 1`, [lng, lat]); if (existing && existing.length > 0) { @@ -469,17 +587,48 @@ export class GeocodingService { } catch (error) { throw error; } } + /** + * الاستيراد الجملي (يستخدمه السكرابر). كان يبتلع كل خطأ بصمت + * (`catch (e) {}`) ويعيد عدد الناجحين فقط — فاستيراد 10 آلاف مكان يسقط منه + * 3 آلاف دون أثر ولا سبب. الآن نُعيد أول 50 خطأ مع رقم السطر. + */ async upsertBatch(places: Partial[]) { const results: any[] = []; - for (const place of places) { try { const r = await this.addPlace(place); results.push({ id: r.id, action: 'created' }); } catch (e) {} } - return { total: places.length, processed: results.length, created: results.length, updated: 0 }; + const errors: { index: number; name?: string; reason: string }[] = []; + + for (const [index, place] of places.entries()) { + try { + const r = await this.addPlace(place); + results.push({ id: r.id, action: 'created' }); + } catch (e) { + if (errors.length < 50) { + errors.push({ + index, + name: (place as any)?.name, + reason: e instanceof HttpException ? e.message : (e?.message ?? 'unknown'), + }); + } + } + } + + const failed = places.length - results.length; + return { + total: places.length, + processed: results.length, + created: results.length, + updated: 0, + failed, + errors, + errorsTruncated: failed > errors.length, + }; } async getRecentPlaces(limit: number = 50) { const s = await this.placesSyriaRepository.find({ order: { created_at: 'DESC' }, take: limit }); const j = await this.placesJordanRepository.find({ order: { created_at: 'DESC' }, take: limit }); const e = await this.placesEgyptRepository.find({ order: { created_at: 'DESC' }, take: limit }); - return [...s, ...j, ...e].sort((a, b) => b.created_at.getTime() - a.created_at.getTime()).slice(0, limit); + const i = await this.placesIraqRepository.find({ order: { created_at: 'DESC' }, take: limit }); + return [...s, ...j, ...e, ...i].sort((a, b) => b.created_at.getTime() - a.created_at.getTime()).slice(0, limit); } async getAllPlacesGeoJSON() { @@ -488,6 +637,7 @@ export class GeocodingService { SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_syria UNION ALL SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_jordan UNION ALL SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_egypt + UNION ALL SELECT id::text, name_ar as name, category, latitude, longitude, address, 'user' as region FROM places_iraq UNION ALL SELECT id::text, COALESCE(names->>'primary', names->>'common', 'Building') as name, 'building' as category, ST_Y(ST_Centroid(location)) as latitude, ST_X(ST_Centroid(location)) as longitude, '' as address, 'overture' as region FROM overture_building WHERE names->>'primary' IS NOT NULL LIMIT 500 UNION ALL SELECT id::text, COALESCE(names->>'primary', names->>'common', 'Street') as name, 'street' as category, ST_Y(ST_Centroid(location)) as latitude, ST_X(ST_Centroid(location)) as longitude, '' as address, 'overture' as region FROM overture_segment WHERE names->>'primary' IS NOT NULL LIMIT 500 UNION ALL SELECT id::text, COALESCE(names->>'primary', names->>'common', 'Place') as name, 'place' as category, ST_Y(ST_Centroid(location)) as latitude, ST_X(ST_Centroid(location)) as longitude, '' as address, 'overture' as region FROM overture_place WHERE names->>'primary' IS NOT NULL LIMIT 500 @@ -520,6 +670,7 @@ export class GeocodingService { case 'syria': return this.placesSyriaRepository; case 'jordan': return this.placesJordanRepository; case 'egypt': return this.placesEgyptRepository; + case 'iraq': return this.placesIraqRepository; default: throw new HttpException('Invalid country: ' + country, HttpStatus.BAD_REQUEST); } } diff --git a/apps/api/src/geocoding/map-refinement.service.ts b/apps/api/src/geocoding/map-refinement.service.ts index d0d466d..de302e1 100644 --- a/apps/api/src/geocoding/map-refinement.service.ts +++ b/apps/api/src/geocoding/map-refinement.service.ts @@ -5,6 +5,7 @@ import { MapCandidate, CandidateStatus, CountryCode } from './entities/map-candi import { PlaceJordan } from './entities/place-jordan.entity'; import { PlaceSyria } from './entities/place-syria.entity'; import { PlaceEgypt } from './entities/place-egypt.entity'; +import { PlaceIraq } from './entities/place-iraq.entity'; @Injectable() export class MapRefinementService { @@ -20,6 +21,8 @@ export class MapRefinementService { private syriaRepository: Repository, @InjectRepository(PlaceEgypt) private egyptRepository: Repository, + @InjectRepository(PlaceIraq) + private iraqRepository: Repository, ) {} async suggestPlace(dto: any, submittedBy: string): Promise { @@ -194,6 +197,7 @@ export class MapRefinementService { switch (candidate.country) { case CountryCode.SYRIA: repo = this.syriaRepository; break; case CountryCode.EGYPT: repo = this.egyptRepository; break; + case CountryCode.IRAQ: repo = this.iraqRepository; break; default: repo = this.jordanRepository; } diff --git a/apps/web/public/style.json b/apps/web/public/style.json index 57f0c76..9d0560a 100644 --- a/apps/web/public/style.json +++ b/apps/web/public/style.json @@ -12,7 +12,6 @@ ], "zoom": 15, "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", @@ -62,7 +61,7 @@ "tiles": [ "https://tiles.intaleqapp.com/overture_building/{z}/{x}/{y}" ], - "maxzoom": 14 + "maxzoom": 16 }, "overture_segments": { "type": "vector", @@ -78,6 +77,13 @@ ], "minzoom": 8, "maxzoom": 18 + }, + "places_iraq": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/places_iraq/{z}/{x}/{y}" + ], + "maxzoom": 14 } }, "layers": [ @@ -85,7 +91,7 @@ "id": "background", "type": "background", "paint": { - "background-color": "#EEF2F7" + "background-color": "#F6F4F0" } }, { @@ -99,7 +105,7 @@ "residential" ], "paint": { - "fill-color": "#F0F4F8", + "fill-color": "#F2EFE9", "fill-opacity": 1 } }, @@ -114,7 +120,7 @@ "commercial" ], "paint": { - "fill-color": "#FAF5EE", + "fill-color": "#F4EFE6", "fill-opacity": 1 } }, @@ -130,10 +136,112 @@ "railway" ], "paint": { - "fill-color": "#E4E8EE", + "fill-color": "#EBE8E2", "fill-opacity": 1 } }, + { + "id": "landuse-retail", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "retail" + ], + "paint": { + "fill-color": "#F5D8D3", + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-farmland", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "farmland", + "farmyard", + "orchard" + ], + "paint": { + "fill-color": "#EFEACB", + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-forest", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "forest", + "natural" + ], + "paint": { + "fill-color": [ + "match", + [ + "get", + "natural" + ], + "wood", + "#B7D6A0", + "#C7E0B4" + ], + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-grass", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "grass", + "meadow" + ], + "paint": { + "fill-color": "#D3E8B8", + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-power", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "==", + "power", + "plant" + ], + [ + "==", + "power", + "substation" + ], + [ + "==", + "landuse", + "industrial" + ] + ], + "paint": { + "fill-color": "#DCC9E8", + "fill-opacity": 0.85, + "fill-outline-color": "#C7ADD9" + } + }, { "id": "landuse-cemetery", "type": "fill", @@ -261,7 +369,7 @@ ] ], "paint": { - "fill-color": "#9ECFE8", + "fill-color": "#A9D5E8", "fill-opacity": 0.95 } }, @@ -297,6 +405,59 @@ "line-opacity": 0.8 } }, + { + "id": "waterway-intermittent", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 11, + "filter": [ + "all", + [ + "in", + "waterway", + "river", + "canal", + "stream", + "drain", + "ditch" + ], + [ + "any", + [ + "==", + "intermittent", + "yes" + ], + [ + "==", + "seasonal", + "yes" + ] + ] + ], + "paint": { + "line-color": "#A8CFE0", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 11, + 0.6, + 16, + 3.5 + ], + "line-dasharray": [ + 3, + 2 + ] + } + }, { "id": "waterway-river", "type": "line", @@ -305,9 +466,53 @@ "filter": [ "all", [ - "in", + "==", + "waterway", + "river" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "!=", + "seasonal", + "yes" + ] + ], + "paint": { + "line-color": "#6BB8D8", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 7, + 0.8, + 14, + 4, + 16, + 7 + ], + "line-opacity": 0.95 + } + }, + { + "id": "waterway-canal", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 11, + "filter": [ + "all", + [ + "==", "waterway", - "river", "canal" ], [ @@ -319,31 +524,24 @@ "!=", "seasonal", "yes" - ], - [ - "!=", - "tunnel", - "yes" ] ], "paint": { - "line-color": "#6BB8D8", + "line-color": "#8FC8DE", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 10, - 1.5, - 14, - 4, + 11, + 0.8, 16, - 7 - ], - "line-opacity": 0.95 + 5 + ] } }, { @@ -620,7 +818,8 @@ "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" @@ -651,22 +850,28 @@ "pedestrian" ], "paint": { - "line-color": "#D4D8DF", + "line-color": "#D6DBE1", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 12, - 1.5, + 12.5, + 1.6, + 15, + 4.5, 16, - 10 + 10, + 18, + 15 ], "line-opacity": 0.7 - } + }, + "minzoom": 12.5 }, { "id": "road-core-minor", @@ -687,17 +892,23 @@ "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 12, - 0.8, + 12.5, + 1.0, + 15, + 3.2, 16, - 8 + 8, + 18, + 12 ] - } + }, + "minzoom": 12.5 }, { "id": "approved-road-casing", @@ -764,22 +975,28 @@ "tertiary_link" ], "paint": { - "line-color": "#C9CED8", + "line-color": "#BCC7D2", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 2, + 11.5, + 1.4, + 14, + 3.4, 16, - 14 + 14, + 18, + 18 ], "line-opacity": 0.75 - } + }, + "minzoom": 11.5 }, { "id": "road-core-tertiary", @@ -793,21 +1010,27 @@ "tertiary_link" ], "paint": { - "line-color": "#FFFFFF", + "line-color": "#DCE5EC", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 1.2, + 11.5, + 0.9, + 14, + 2.4, 16, - 11 + 11, + 18, + 14 ] - } + }, + "minzoom": 11.5 }, { "id": "road-casing-secondary", @@ -821,22 +1044,28 @@ "secondary_link" ], "paint": { - "line-color": "#C4CFDE", + "line-color": "#98AABC", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 2.5, + 10, + 1.4, + 13, + 3.2, 16, - 16 + 16, + 18, + 21 ], "line-opacity": 0.8 - } + }, + "minzoom": 10 }, { "id": "road-core-secondary", @@ -850,21 +1079,27 @@ "secondary_link" ], "paint": { - "line-color": "#F8FBFF", + "line-color": "#BACAD8", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 1.8, + 10, + 0.9, + 13, + 2.2, 16, - 13 + 13, + 18, + 17 ] - } + }, + "minzoom": 10 }, { "id": "road-casing-primary", @@ -878,22 +1113,28 @@ "primary_link" ], "paint": { - "line-color": "#C8B868", + "line-color": "#71889E", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 10, - 3, + 8, + 1.4, + 12, + 3.4, 16, - 18 + 18, + 18, + 24 ], "line-opacity": 0.7 - } + }, + "minzoom": 8 }, { "id": "road-core-primary", @@ -907,21 +1148,27 @@ "primary_link" ], "paint": { - "line-color": "#EDD870", + "line-color": "#93A9BC", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 10, - 2, + 8, + 0.9, + 12, + 2.4, 16, - 14 + 14, + 18, + 19 ] - } + }, + "minzoom": 8 }, { "id": "road-casing-motorway-trunk", @@ -937,22 +1184,28 @@ "trunk_link" ], "paint": { - "line-color": "#C8A84B", + "line-color": "#4E6478", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 9, - 4, + 5, + 1.4, + 10, + 3.2, 16, - 20 + 20, + 18, + 26 ], "line-opacity": 0.75 - } + }, + "minzoom": 5 }, { "id": "road-core-motorway-trunk", @@ -968,91 +1221,27 @@ "trunk_link" ], "paint": { - "line-color": "#F0C040", + "line-color": "#6B8299", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 9, - 2.5, + 5, + 0.8, + 10, + 2.2, + 16, 16, - 16 - ] - } - }, - { - "id": "road-direction-arrows", - "type": "symbol", - "source": "local-osm-lines", - "source-layer": "planet_osm_line", - "minzoom": 15, - "filter": [ - "all", - [ - "in", - "highway", - "motorway", - "trunk", - "primary", - "secondary", - "tertiary", - "residential", - "unclassified", - "living_street" - ], - [ - "==", - "oneway", - "yes" - ] - ], - "layout": { - "symbol-placement": "line", - "symbol-spacing": 80, - "text-field": "▸", - "text-font": [ - "Noto Sans Regular" - ], - "text-size": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 15, - 12, 18, - 18 - ], - "text-keep-upright": false, - "text-rotation-alignment": "map", - "text-allow-overlap": true, - "text-ignore-placement": true, - "text-padding": 0 + 21 + ] }, - "paint": { - "text-color": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 15, - "rgba(100,116,139,0.4)", - 18, - "rgba(100,116,139,0.7)" - ], - "text-halo-color": "rgba(255,255,255,0.3)", - "text-halo-width": 0.5 - } + "minzoom": 5 }, { "id": "building-fill-flat", @@ -1063,11 +1252,10 @@ "has", "building" ], - "maxzoom": 14, "paint": { - "fill-color": "#DDD8D0", - "fill-opacity": 0.85, - "fill-outline-color": "#C4BEB4" + "fill-color": "#E4DED4", + "fill-opacity": 1, + "fill-outline-color": "#C8C0B2" } }, { @@ -1075,7 +1263,7 @@ "type": "fill-extrusion", "source": "overture_buildings", "source-layer": "overture_building", - "minzoom": 14, + "minzoom": 16, "layout": { "visibility": "visible" }, @@ -1153,6 +1341,202 @@ "fill-extrusion-vertical-gradient": true } }, + { + "id": "overture-building-footprint", + "type": "fill", + "source": "overture_buildings", + "source-layer": "overture_building", + "paint": { + "fill-color": "#E4DED4", + "fill-opacity": 1, + "fill-outline-color": "#C8C0B2" + }, + "minzoom": 14 + }, + { + "id": "bridge-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 12, + "filter": [ + "all", + [ + "==", + "bridge", + "yes" + ], + [ + "in", + "highway", + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "residential", + "unclassified", + "living_street", + "motorway_link", + "trunk_link", + "primary_link", + "secondary_link" + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "round" + }, + "paint": { + "line-color": "#5A6B7C", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 12, + 5, + 16, + 22, + 18, + 30 + ] + } + }, + { + "id": "bridge-core", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 12, + "filter": [ + "all", + [ + "==", + "bridge", + "yes" + ], + [ + "in", + "highway", + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "residential", + "unclassified", + "living_street", + "motorway_link", + "trunk_link", + "primary_link", + "secondary_link" + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "round" + }, + "paint": { + "line-color": [ + "match", + [ + "get", + "highway" + ], + "motorway", + "#6B8299", + "trunk", + "#6B8299", + "motorway_link", + "#6B8299", + "trunk_link", + "#6B8299", + "primary", + "#93A9BC", + "primary_link", + "#93A9BC", + "secondary", + "#BACAD8", + "secondary_link", + "#BACAD8", + "tertiary", + "#DCE5EC", + "#FFFFFF" + ], + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 12, + 3.5, + 16, + 18, + 18, + 25 + ] + } + }, + { + "id": "road-direction-arrows", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 15, + "filter": [ + "all", + [ + "in", + "highway", + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "residential", + "unclassified", + "living_street" + ], + [ + "==", + "oneway", + "yes" + ] + ], + "layout": { + "symbol-placement": "line", + "symbol-spacing": 90, + "icon-image": "arrow", + "icon-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 15, + 0.35, + 18, + 0.55 + ], + "icon-rotation-alignment": "map", + "icon-allow-overlap": true, + "icon-ignore-placement": true + }, + "paint": { + "icon-opacity": 0.75 + } + }, { "id": "railway-label", "type": "symbol", @@ -1713,6 +2097,230 @@ "text-halo-width": 2 } }, + { + "id": "poi-shop", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "all", + [ + "has", + "shop" + ], + [ + "!in", + "shop", + "vacant", + "no" + ] + ], + "layout": { + "icon-image": "shop", + "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": "#7B3FA0", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-police", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 15, + "filter": [ + "in", + "amenity", + "police", + "fire_station" + ], + "layout": { + "icon-image": "police", + "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": "#2F5AA8", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-bank", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "in", + "amenity", + "bank", + "bureau_de_change", + "atm" + ], + "layout": { + "icon-image": "shop", + "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": "#2E6B4F", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-fuel", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 14, + "filter": [ + "==", + "amenity", + "fuel" + ], + "layout": { + "icon-image": "shop", + "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": "#B5651D", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-hotel", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "in", + "tourism", + "hotel", + "motel", + "guest_house", + "hostel" + ], + "layout": { + "icon-image": "tourist", + "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": "#8A5A8A", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, { "id": "place-labels-area", "type": "symbol", @@ -2050,15 +2658,57 @@ } }, { - "id": "overture-building-footprint", - "type": "fill", - "source": "overture_buildings", - "source-layer": "overture_building", - "maxzoom": 14, + "id": "places-iraq-labels", + "type": "symbol", + "source": "places_iraq", + "source-layer": "places_iraq", + "minzoom": 10, + "filter": [ + "!in", + "category", + "street" + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name_ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Bold" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 12, + 9, + 16, + 13 + ], + "text-offset": [ + 0, + 1.5 + ], + "text-anchor": "top", + "text-padding": 8, + "text-allow-overlap": false + }, "paint": { - "fill-color": "#DDD8D0", - "fill-opacity": 0.85, - "fill-outline-color": "#C4BEB4" + "text-color": "#2D3748", + "text-halo-color": "rgba(255, 255, 255, 0.9)", + "text-halo-width": 2 } }, { @@ -2069,14 +2719,14 @@ "minzoom": 16, "filter": [ "has", - "names" + "name_primary" ], "layout": { "text-field": [ "coalesce", [ "get", - "names" + "name_primary" ], "" ], @@ -2102,14 +2752,14 @@ "minzoom": 14, "filter": [ "has", - "names" + "name_primary" ], "layout": { "text-field": [ "coalesce", [ "get", - "names" + "name_primary" ], "" ], @@ -2149,4 +2799,4 @@ } } ] -} +} \ No newline at end of file diff --git a/apps/web/src/components/MapComponent.tsx b/apps/web/src/components/MapComponent.tsx index 64e54b5..d38a9e7 100644 --- a/apps/web/src/components/MapComponent.tsx +++ b/apps/web/src/components/MapComponent.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useRef, useState } from 'react'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; +import { attachIconLoader } from '../utils/mapIcons'; interface MapComponentProps { onMapLoad: (map: maplibregl.Map) => void; onMapClick?: (lat: number, lng: number) => void; @@ -59,7 +60,6 @@ const MapComponent: React.FC = ({ style: { version: 8, 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', @@ -390,6 +390,8 @@ const MapComponent: React.FC = ({ }); map.current = initialMap; + // آيقونات الـ POI تُحمّل عند الطلب بدل sprite مستضاف + attachIconLoader(initialMap); // Request User Location if ('geolocation' in navigator) { diff --git a/apps/web/src/pages/CompareView.tsx b/apps/web/src/pages/CompareView.tsx index b95a40e..6898b24 100644 --- a/apps/web/src/pages/CompareView.tsx +++ b/apps/web/src/pages/CompareView.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useRef, useState } from 'react'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; +import { attachIconLoader } from '../utils/mapIcons'; /** * CompareView — side-by-side, pan/zoom-synced map comparison. @@ -55,6 +56,8 @@ const CompareView: React.FC = () => { // already point at the absolute Martin host), so load it relatively — never // prefixed with the tile host, which does not serve /style.json. const lMap = new maplibregl.Map({ container: leftDiv.current, style: '/style.json', center: start, zoom: startZoom, attributionControl: false }); + // آيقونات الـ POI تُحمّل عند الطلب — يجب الربط قبل أول رسم للطبقات + attachIconLoader(lMap); const rMap = new maplibregl.Map({ container: rightDiv.current, style: rasterStyle(REFS['esri-sat']), center: start, zoom: startZoom, attributionControl: false }); lMap.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-left'); rMap.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right'); diff --git a/apps/web/src/utils/mapIcons.ts b/apps/web/src/utils/mapIcons.ts new file mode 100644 index 0000000..830efab --- /dev/null +++ b/apps/web/src/utils/mapIcons.ts @@ -0,0 +1,67 @@ +import type { Map as MlMap } from 'maplibre-gl'; + +/** + * تحميل آيقونات الـ POI عند الطلب من ملفات SVG في /icons. + * + * لماذا لا sprite: الستايل كان يشير إلى demotiles.maplibre.org (خادم MapLibre + * التجريبي). حين يفشل تحميله يرسم MapLibre نص التسمية بلا آيقونة وبصمت — فتظهر + * الخريطة بأسماء بلا رموز. البديل المعتاد هو بناء sprite sheet، لكنه يتطلب + * سلسلة أدوات (cairo/resvg) واستضافة ملف إضافي ومزامنته مع كل آيقونة جديدة. + * + * `styleimagemissing` ينطلق مرة واحدة لكل آيقونة تطلبها طبقة ولا يجدها المحرك، + * فنحمّلها وقتها من الـ SVG الموجود أصلاً في المستودع. لا خطوة بناء، ولا ملف + * يُستضاف، وإضافة آيقونة جديدة = إسقاط ملف SVG في المجلد فقط. + * + * ALIASES تسدّ الفجوة بين ما تطلبه طبقات الستايل وما هو متوفر فعلاً كملف. + */ +const ALIASES: Record = { + rail: 'train', + college: 'tourist', + school: 'tourist', + cafe: 'cafe', + restaurant: 'restaurant', +}; + +const SIZE = 24; + +export function attachIconLoader(map: MlMap): () => void { + const pending = new Set(); + + const onMissing = (e: { id: string }) => { + const id = e.id; + // الحدث قد يتكرر لنفس الآيقونة قبل اكتمال التحميل غير المتزامن + if (!id || pending.has(id) || map.hasImage(id)) return; + pending.add(id); + + const file = ALIASES[id] ?? id; + const img = new Image(SIZE, SIZE); + img.crossOrigin = 'anonymous'; + + img.onload = () => { + // الفحص مكرر عمداً: التحميل غير متزامن وقد يكون الستايل تبدّل أثناءه + if (!map.hasImage(id)) { + try { + map.addImage(id, img, { pixelRatio: 1 }); + } catch { + /* الستايل تبدّل تحتنا — تُطلب مجدداً عند الحاجة */ + } + } + pending.delete(id); + }; + + img.onerror = () => { + // بلا ملف مقابل: نضيف بكسلاً شفافاً حتى لا يعيد MapLibre إطلاق الحدث + // في كل إطار. النص يظهر بلا رمز، وهو أفضل من حلقة لا تنتهي. + if (!map.hasImage(id)) { + map.addImage(id, { width: 1, height: 1, data: new Uint8Array(4) }); + } + pending.delete(id); + console.warn(`[mapIcons] لا يوجد /icons/${file}.svg للآيقونة "${id}"`); + }; + + img.src = `/icons/${file}.svg`; + }; + + map.on('styleimagemissing', onMissing); + return () => map.off('styleimagemissing', onMissing); +} diff --git a/docker-compose.yml b/docker-compose.yml index a4f9a39..a17377f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,11 +46,14 @@ services: ports: - "8989:8080" environment: - - JAVA_OPTS=-Xmx8g -Xms1g + # 7.8GB إجمالي على السيرفر. باقي الحاويات ~1.3GB + النظام ~0.5GB. + # Xmx5g كان ينتج RSS ~6GB فيصطدم بالسقف ويُقتل بالـ OOM لحظة عودة باقي + # الحاويات — رغم أن البناء نفسه ينجح حين تكون موقوفة. 4g تترك هامشاً حقيقياً. + - JAVA_OPTS=-Xmx4g -Xms1g deploy: resources: limits: - memory: 10g + memory: 6g volumes: - ./infrastructure/osm-data:/data - ./infrastructure/docker/graphhopper/config.yml:/graphhopper/config.yml diff --git a/docs/ROUTING_ONEWAY_AR.md b/docs/ROUTING_ONEWAY_AR.md new file mode 100644 index 0000000..0fb5d27 --- /dev/null +++ b/docs/ROUTING_ONEWAY_AR.md @@ -0,0 +1,110 @@ +# اتجاهات السير في محرك التوجيه — قاعدة لا تُكسر + +> سجلّ عطل حقيقي كلّف يومين (26–28 يوليو 2026). اقرأ هذا قبل أي تعديل على +> `infrastructure/docker/graphhopper/config.yml`. + +## القاعدة + +`custom_model` المكتوب **سطرياً** داخل `profiles` لا يرث القواعد الافتراضية +لملف `car.json`. أهم قاعدة ساقطة هي التي تفرض اتجاه السير: + +```yaml +- if: "!car_access" + multiply_by: 0 +``` + +بدونها تبقى `car_access` مخزّنة في الغراف (تراها في اللوغ) لكنها **لا تدخل في +حساب الوزن إطلاقاً**. النتيجة: المحرك يتجاهل كل `oneway` وكل `junction=roundabout` +ويمشي في الاتجاهين على كل شارع — يبدو للمستخدم «كأنه يمشي كالمشاة». + +القاعدة تتطلّب وجود `car_access` ضمن `graph.encoded_values`، وإلا يرفض المحرك +الإقلاع بـ `Encoded values missing: car_access`. + +**السطران الإلزاميان في config.yml:** + +```yaml +graph.encoded_values: ...,car_access # car_access إلزامية + +custom_model: + priority: + - if: "!car_access" # أول قاعدة في priority + multiply_by: 0 +``` + +## كيف يبدو العطل + +| العرض | الملاحظة | +|---|---| +| `A→B` و `B→A` بنفس المسافة تماماً على شارع أحادي | البصمة القاطعة | +| المسار يقطع دواراً مستقيماً بلا تعليمة `sign: 6` | حلقات الدوار أحادية ضمناً، فتُفتح بالاتجاهين | +| `details=car_access` يُظهر `False` على مقطع ضمن مسار مقبول | المحرك مرّ عبر حافة ممنوعة | + +## تشخيص سريع + +```bash +bash infrastructure/scripts/verify-oneway.sh +``` + +الفحص اليدوي لمسار بعينه: + +```bash +curl -s "http://localhost:8989/route?point=LAT1,LON1&point=LAT2,LON2&profile=car&details=car_access&details=roundabout" | python3 -m json.tool +``` + +## ⚠️ تحذير: check-bidirectional.sh منطقه معكوس + +`check-bidirectional.sh` يعتبر **اختلاف** الذهاب عن العودة فشلاً، وينصح بتشغيل +`normalize-oneway.sh`. هذا خطأ: الاختلاف على شارع أحادي هو السلوك **الصحيح**. +لا تتبع نصيحته، ولا تشغّل `normalize-oneway.sh apply` إلا بعد التحقق ميدانياً +من أن الشارع ثنائي الاتجاه فعلاً على الأرض. + +`normalize_oneway.py` يكتب `oneway=no` على البيانات نفسها. تعديل البيانات لإخفاء +عرَض ناتج عن خطأ إعداد يُنتج مسارات غير قانونية للسائقين. + +## متى يكون السبب البيانات لا الإعداد + +إن كان `verify-oneway.sh` ناجحاً وما زال مسار بعينه خاطئاً، فالنقص في OSM: + +```bash +osmium extract -b LON1,LAT1,LON2,LAT2 infrastructure/osm-data/master_map.osm.pbf -o /tmp/a.pbf --overwrite +osmium cat -f opl /tmp/a.pbf | grep '^w' | grep -c 'junction=roundabout' +``` + +صفر = الدوار غير مرسوم. الحل: ارسمه على openstreetmap.org — يصل تلقائياً في +دورة التحديث القادمة ويفيد الجميع. البديل الموضعي هو `approved_roads` عبر +`apply-delta.sh`، لكنه يحتاج صيانة يدوية لكل حالة. + +## الصورة + +نستخدم GraphHopper 11.0 الرسمي مبنياً محلياً من +[`infrastructure/docker/graphhopper/Dockerfile`](../infrastructure/docker/graphhopper/Dockerfile). + +الصورة السابقة `israelhikingmap/graphhopper` كانت تتجاهل `graph.location` +(تبني في `/data/default-gh`) وتتجاهل `JAVA_OPTS`. لا تعُد إليها. + +`ENTRYPOINT` يستخدم `sh -c` عمداً — الصيغة exec المباشرة لا توسّع `$JAVA_OPTS` +فيبقى الـ heap على الافتراضي. + +## الطرق المرسومة (approved_roads) + +`update-data.sh` يستدعي `apply-delta.sh` بعد كل دمج، فيصدّر `approved_roads` +كـ OSM XML ويدمجها في `master_map.osm.pbf`. الطرق المرسومة تنجو من كل تحديث. + +الاتصال بالشبكة يعتمد على `start_node` / `end_node` (معرّفات OSM حقيقية تُحلّ +وقت الموافقة). بدونهما تُنشأ عقد جديدة بمعرّفات سالبة ويصبح الطريق **معزولاً** — +موجود في الخريطة لكن لا يمر به التوجيه. + +`oneway` يُصدَّر أيضاً: `1` → `yes`، `-1` → `-1`. أي أن الاتجاه المرسوم محترم، +شرط أن تكون قاعدة `!car_access` أعلاه موجودة. + +## بعد أي تعديل على config.yml + +```bash +docker compose stop routing +rm -rf infrastructure/osm-data/graph-cache infrastructure/osm-data/default-gh +docker compose up -d routing && docker compose logs -f routing # انتظر "Started Server" +bash infrastructure/scripts/verify-oneway.sh +``` + +البناء ~4 دقائق. إن ظهر `exited with code 137` فهو OOM — أوقف +`api web dashboard martin redis` أثناء البناء، أو أنزل `-Xmx` في docker-compose. diff --git a/infrastructure/scripts/build-sprite.py b/infrastructure/scripts/build-sprite.py new file mode 100644 index 0000000..206b796 --- /dev/null +++ b/infrastructure/scripts/build-sprite.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +""" +build-sprite.py — يبني sprite sheet لـ MapLibre من مجلد SVG. + +لماذا: MapLibre لا يقرأ ملفات SVG مفردة. يحتاج صورة PNG واحدة تُرصف فيها كل +الآيقونات + فهرس JSON يحدد موضع وحجم كل واحدة. كان الستايل يشير إلى +demotiles.maplibre.org (خادم تجريبي)، وحين يفشل تحميله يرسم MapLibre النص +بلا آيقونة وبصمت — فتبدو الخريطة بأسماء بلا رموز. + +الاستخدام: + pip install cairosvg pillow + python3 infrastructure/scripts/build-sprite.py + +المخرجات في apps/web/public/: + sprite.png / sprite.json (1x) + sprite@2x.png / sprite@2x.json (شاشات Retina — MapLibre يطلبها تلقائياً) + +ثم في الستايل: "sprite": "https://map-saas.intaleqapp.com/sprite" +(بلا امتداد — MapLibre يضيف .png/.json و@2x بنفسه) +""" +import json +import os +import sys + +try: + import cairosvg + from PIL import Image +except ImportError: + sys.exit("ينقص: pip install cairosvg pillow") + +ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +ICONS_DIR = os.path.join(ROOT, "apps", "web", "public", "icons") +OUT_DIR = os.path.join(ROOT, "apps", "web", "public") + +BASE_SIZE = 24 # بكسل للآيقونة عند 1x +PADDING = 2 # فاصل يمنع نزّ ألوان الجيران عند التصغير (bleeding) + + +def build(scale: int, suffix: str) -> None: + size = BASE_SIZE * scale + pad = PADDING * scale + + svgs = sorted(f for f in os.listdir(ICONS_DIR) if f.endswith(".svg")) + if not svgs: + sys.exit(f"لا توجد ملفات SVG في {ICONS_DIR}") + + rendered = [] + for fname in svgs: + name = os.path.splitext(fname)[0] + png_bytes = cairosvg.svg2png( + url=os.path.join(ICONS_DIR, fname), + output_width=size, + output_height=size, + ) + import io + rendered.append((name, Image.open(io.BytesIO(png_bytes)).convert("RGBA"))) + + # شبكة مربعة تقريباً: أقل هدراً من صف واحد طويل + cols = max(1, int(len(rendered) ** 0.5 + 0.999)) + rows = (len(rendered) + cols - 1) // cols + + sheet = Image.new( + "RGBA", + (cols * (size + pad) - pad, rows * (size + pad) - pad), + (0, 0, 0, 0), + ) + + index = {} + for i, (name, img) in enumerate(rendered): + x = (i % cols) * (size + pad) + y = (i // cols) * (size + pad) + sheet.paste(img, (x, y)) + index[name] = { + "x": x, "y": y, + "width": size, "height": size, + "pixelRatio": scale, + "sdf": False, + } + + sheet.save(os.path.join(OUT_DIR, f"sprite{suffix}.png")) + with open(os.path.join(OUT_DIR, f"sprite{suffix}.json"), "w") as fh: + json.dump(index, fh, indent=2) + + print(f"✅ sprite{suffix}: {len(index)} آيقونة، {sheet.width}×{sheet.height}px") + return list(index) + + +if __name__ == "__main__": + names = build(1, "") + build(2, "@2x") + print("\nالآيقونات المتاحة لـ icon-image:") + print(" " + ", ".join(names)) + print("\n⚠️ طبقات الستايل تطلب أسماء قد لا تكون موجودة أعلاه") + print(" (college, rail, tourist ...) — راجعها أو أضف SVG مقابلاً لكل ناقص.") diff --git a/infrastructure/scripts/check-bidirectional.sh b/infrastructure/scripts/check-bidirectional.sh index c687f27..8b19cb5 100755 --- a/infrastructure/scripts/check-bidirectional.sh +++ b/infrastructure/scripts/check-bidirectional.sh @@ -56,8 +56,11 @@ for c in "${CASES[@]}"; do if [ "$1" = "ok" ]; then echo " ✓ متقارب (نسبة ${2})" else - echo " ✗ فارق كبير (نسبة ${2:-؟}) — اتجاه واحد مفروض أو التفاف إجباري" - echo " شغّل: bash infrastructure/scripts/normalize-oneway.sh audit" + echo " ⚠ فارق كبير (نسبة ${2:-؟}) — اتجاه واحد مفروض أو التفاف إجباري" + echo " هذا ليس عطلاً بالضرورة: على شارع أحادي فعلاً هذا هو السلوك الصحيح." + echo " تحقّق ميدانياً أولاً. لا تشغّل normalize-oneway.sh apply قبل ذلك —" + echo " فهو يكتب oneway=no على البيانات وينتج مسارات غير قانونية." + echo " اقرأ: docs/ROUTING_ONEWAY_AR.md" FAIL=1 fi done diff --git a/infrastructure/scripts/overture_ingest.sh b/infrastructure/scripts/overture_ingest.sh index 9b65b7a..cde8cce 100644 --- a/infrastructure/scripts/overture_ingest.sh +++ b/infrastructure/scripts/overture_ingest.sh @@ -1,82 +1,170 @@ #!/bin/bash +# -------------------------------------------------------------------------- +# overture_ingest.sh — سحب وحقن بيانات Overture (مباني، شوارع، أماكن، حدود) +# +# ⚠️ تغيير جوهري عن النسخة القديمة: +# القديمة كانت تبدأ بـ DROP TABLE overture_place, overture_building, +# overture_segment — أي أن تشغيلها لأي دولة يمسح بيانات كل الدول الأخرى. +# الآن الحذف *محصور بالنطاق الجغرافي* الجاري إعادة حقنه فقط، فتبقى بيانات +# بقية الدول سليمة. هذا يجعل السكربت قابلاً للتشغيل الجزئي وإعادة التشغيل. +# +# الاستخدام: +# bash infrastructure/scripts/overture_ingest.sh # كل النطاقات +# bash infrastructure/scripts/overture_ingest.sh baghdad # نطاق واحد +# bash infrastructure/scripts/overture_ingest.sh iraq baghdad # عدة نطاقات +# +# ملاحظة عن الأحجام: المباني تُسحب على مستوى المدينة لا الدولة — مباني دولة +# كاملة قد تتجاوز عدة جيجابايت GeoJSON ولا يحتملها قرص السيرفر الحالي. +# لذلك: building/segment ← نطاق مدينة، place/division_area ← نطاق دولة. +# -------------------------------------------------------------------------- +set -euo pipefail -# overture_ingest.sh - Data pipeline for Overture Maps -# سكريبت سحب وحقن بيانات المباني والشوارع +APP_DIR="${APP_DIR:-/home/hamzadoctor/app}" +DB_HOST="${DB_HOST:-localhost}" +DB_PORT="${DB_PORT:-5432}" +DB_USER="${DB_USER:-mapuser}" +DB_NAME="${DB_NAME:-mapdb}" +DB_PASS="${DB_PASS:-mappass}" -set -e +cd "$APP_DIR" -# Configuration -DB_HOST="localhost" -DB_PORT="5432" -DB_USER="mapuser" -DB_NAME="mapdb" -DB_PASS="mappass" - -# Jordan North BBOX (Amman, Irbid, Zarqa) +# ── النطاقات: "minlon,minlat,maxlon,maxlat" ─────────────────────────────── +# نطاقات الدول (للأماكن والحدود الإدارية) BBOX_JORDAN_NORTH="35.5,31.7,39.3,33.4" -# Jordan South BBOX (Aqaba, Karak, Ma'an) BBOX_JORDAN_SOUTH="34.9,29.1,38.0,31.7" -# Syria BBOX (Full Country) BBOX_SYRIA="35.7,32.3,42.4,37.3" +BBOX_EGYPT="24.7,21.9,36.9,31.7" +BBOX_IRAQ="38.7,29.0,48.8,37.4" -echo "🚀 Starting Overture Data Pipeline (Jordan & Syria)..." +# نطاقات المدن (للمباني والشوارع — أثقل بكثير) +BBOX_AMMAN="35.75,31.75,36.15,32.15" +BBOX_DAMASCUS="36.15,33.40,36.45,33.62" +BBOX_CAIRO="31.10,29.85,31.50,30.20" +# بغداد المركز فقط (~25×17كم). النطاق الأوسع 44.10,33.10,44.70,33.50 تجاوز +# 632 ألف مبنى وملأ قرص السيرفر قبل أن ينتهي. وسّعه بعد ترقية التخزين. +BBOX_BAGHDAD="44.28,33.25,44.55,33.40" -echo "🧹 Clearing old Overture tables to prevent duplication..." -docker compose -f /home/hamzadoctor/app/docker-compose.yml exec -T db psql -U $DB_USER -d $DB_NAME -c "DROP TABLE IF EXISTS overture_place, overture_building, overture_segment;" +psql_run() { + docker compose -f "${APP_DIR}/docker-compose.yml" exec -T db \ + psql -U "$DB_USER" -d "$DB_NAME" "$@" +} -# 1. Setup Python Environment -# ... (rest of setup) -if [ ! -d "venv_overture" ]; then - echo "📦 Creating virtual environment..." - python3 -m venv venv_overture -fi -source venv_overture/bin/activate -pip install --upgrade pip -pip install overturemaps +# ── حذف محصور بالنطاق: ينظّف ما سنعيد حقنه فقط، ولا يمس الدول الأخرى ────── +purge_bbox() { + local table=$1 bbox=$2 + IFS=',' read -r minx miny maxx maxy <<< "$bbox" + # to_regclass يمنع الفشل في أول تشغيل قبل أن ينشئ ogr2ogr الجدول + psql_run -c " + DO \$\$ + BEGIN + IF to_regclass('public.${table}') IS NOT NULL THEN + DELETE FROM ${table} + WHERE location && ST_MakeEnvelope(${minx}, ${miny}, ${maxx}, ${maxy}, 4326); + END IF; + END \$\$; + " +} -# 2. Function to download and ingest -process_city() { - local city=$1 - local bbox=$2 - local theme=$3 # building, segment, or division_area - - echo "🌍 Processing $city - Theme: $theme..." - output_file="overture_${city}_${theme}.geojson" - - # Download +process() { + local region=$1 bbox=$2 theme=$3 + local output_file="overture_${region}_${theme}.geojson" + + echo "🌍 [$region] السمة: $theme ..." overturemaps download --bbox="$bbox" -f geojson --type="$theme" -o "$output_file" - - # If division_area, we use our NestJS API to import for better control + if [ "$theme" == "division_area" ]; then - echo "📍 Administrative data downloaded: $output_file" - echo "💡 Use the NestJS /geocoding/import-boundaries endpoint to ingest this file." + echo "📍 الحدود الإدارية جاهزة: $output_file" + echo " استوردها عبر: POST /geocoding/import-boundaries?country=&filePath=/data/${output_file}" return fi - # Ingest into PostGIS for buildings and segments - echo "🔌 Injecting into PostGIS (Table: overture_${theme})..." + echo "🧹 تنظيف نطاق ${region} من overture_${theme} (بدون المساس بغيره)..." + purge_bbox "overture_${theme}" "$bbox" + + echo "🔌 الحقن في PostGIS (overture_${theme})..." export PGPASSWORD=$DB_PASS - ogr2ogr -f "PostgreSQL" \ + # `if !` ضروري: مع set -e يخرج السكربت فوراً عند فشل ogr2ogr فلا يُحذف + # الملف المؤقت. ملف مباني مدينة كبيرة قد يبلغ عدة جيجابايت، وتَركه بعد + # فشل سببه امتلاء القرص أصلاً يجعل المحاولة التالية تفشل أسرع. + if ! ogr2ogr -f "PostgreSQL" \ PG:"host=$DB_HOST port=$DB_PORT user=$DB_USER dbname=$DB_NAME password=$DB_PASS" \ "$output_file" \ -nln "overture_${theme}" \ -update -append \ -nlt PROMOTE_TO_MULTI \ -lco GEOMETRY_NAME=location - - echo "✅ Finished $city $theme" - rm "$output_file" + then + echo "❌ فشل حقن ${region}/${theme} — يُحذف الملف المؤقت ($(du -h "$output_file" 2>/dev/null | cut -f1))" + rm -f "$output_file" + return 1 + fi + + echo "✅ انتهى ${region}/${theme}" + rm -f "$output_file" } -# 3. Execution (Jordan & Syria) -# Jordan -process_city "jordan_north" "$BBOX_JORDAN_NORTH" "division_area" -process_city "jordan_north" "$BBOX_JORDAN_NORTH" "place" -process_city "jordan_south" "$BBOX_JORDAN_SOUTH" "division_area" -process_city "jordan_south" "$BBOX_JORDAN_SOUTH" "place" +# ── تجهيز بيئة بايثون ───────────────────────────────────────────────────── +if [ ! -d "venv_overture" ]; then + echo "📦 إنشاء بيئة افتراضية..." + python3 -m venv venv_overture +fi +source venv_overture/bin/activate +pip install --quiet --upgrade pip +pip install --quiet overturemaps -# Syria -process_city "syria" "$BBOX_SYRIA" "division_area" -process_city "syria" "$BBOX_SYRIA" "place" +# ── تعريف النطاقات القابلة للتشغيل ──────────────────────────────────────── +run_jordan() { process jordan_north "$BBOX_JORDAN_NORTH" division_area + process jordan_north "$BBOX_JORDAN_NORTH" place + process jordan_south "$BBOX_JORDAN_SOUTH" division_area + process jordan_south "$BBOX_JORDAN_SOUTH" place; } +run_syria() { process syria "$BBOX_SYRIA" division_area + process syria "$BBOX_SYRIA" place; } +run_egypt() { process egypt "$BBOX_EGYPT" division_area + process egypt "$BBOX_EGYPT" place; } +run_iraq() { process iraq "$BBOX_IRAQ" division_area + process iraq "$BBOX_IRAQ" place; } -echo "🎉 Administrative boundary GeoJSONs ready for import!" +run_amman() { process amman "$BBOX_AMMAN" building + process amman "$BBOX_AMMAN" segment; } +run_damascus() { process damascus "$BBOX_DAMASCUS" building + process damascus "$BBOX_DAMASCUS" segment; } +run_cairo() { process cairo "$BBOX_CAIRO" building + process cairo "$BBOX_CAIRO" segment; } +run_baghdad() { process baghdad "$BBOX_BAGHDAD" building + process baghdad "$BBOX_BAGHDAD" segment; } + +ALL_REGIONS=(jordan syria egypt iraq amman damascus cairo baghdad) +TARGETS=("$@") +if [ ${#TARGETS[@]} -eq 0 ]; then + TARGETS=("${ALL_REGIONS[@]}") +fi + +echo "🚀 نطاقات هذا التشغيل: ${TARGETS[*]}" +for r in "${TARGETS[@]}"; do + if ! declare -F "run_${r}" >/dev/null; then + echo "❌ نطاق غير معروف: ${r} — المتاح: ${ALL_REGIONS[*]}" + exit 2 + fi + "run_${r}" +done + +# ── فهارس مكانية + تسطيح الأسماء (idempotent) ───────────────────────────── +# name_primary إلزامي: عمود names من نوع json وبلاطات MVT لا تحمل كائنات، فلا +# يستطيع الستايل قراءة الاسم منه. التفاصيل: infrastructure/sql/09_overture_name_primary.sql +for t in overture_building overture_segment overture_place; do + psql_run -c " + DO \$\$ + BEGIN + IF to_regclass('public.${t}') IS NOT NULL THEN + EXECUTE 'CREATE INDEX IF NOT EXISTS idx_${t}_location ON ${t} USING gist (location)'; + EXECUTE 'ALTER TABLE ${t} ADD COLUMN IF NOT EXISTS name_primary text'; + EXECUTE 'UPDATE ${t} SET name_primary = names->>''primary'' + WHERE names IS NOT NULL AND name_primary IS NULL'; + EXECUTE 'CREATE INDEX IF NOT EXISTS idx_${t}_name_primary ON ${t} (name_primary) + WHERE name_primary IS NOT NULL'; + END IF; + END \$\$; + " +done + +echo "🎉 اكتمل. البلاطات تُخدم عبر martin تلقائياً (WATCH_DB=true)." diff --git a/infrastructure/scripts/update-data.sh b/infrastructure/scripts/update-data.sh index e5779c3..dcaafee 100644 --- a/infrastructure/scripts/update-data.sh +++ b/infrastructure/scripts/update-data.sh @@ -11,7 +11,7 @@ set -e # Exit on error -echo "🚀 Starting 10-day map update (v2 — Jordan + Syria + Egypt + Delta)..." +echo "🚀 Starting 10-day map update (v3 — Jordan + Syria + Egypt + Iraq + Delta)..." APP_DIR="/home/hamzadoctor/app" DATA_DIR="${APP_DIR}/infrastructure/osm-data" @@ -21,27 +21,28 @@ DELTA_FILE="${DATA_DIR}/delta.osm" cd "${APP_DIR}" -# ── Step 1: Download all three country PBFs ──────────────────────────────── -echo "🌍 Downloading Jordan, Syria & Egypt PBF data from Geofabrik..." +# ── Step 1: Download all four country PBFs ───────────────────────────────── +echo "🌍 Downloading Jordan, Syria, Egypt & Iraq PBF data from Geofabrik..." wget -q --show-progress -O "${DATA_DIR}/jordan-latest.osm.pbf.new" \ "https://download.geofabrik.de/asia/jordan-latest.osm.pbf" -wget -q --show-progress -O "${DATA_DIR}/syria-latest.osm.pbf.new" \ - "https://download.geofabrik.de/africa/egypt-latest.osm.pbf" -# ✅ FIX: Syria URL was accidentally downloading Egypt above in old script — corrected -wget -q --show-progress -O "${DATA_DIR}/egypt-latest.osm.pbf.new" \ - "https://download.geofabrik.de/africa/egypt-latest.osm.pbf" +# ✅ FIX: كان يحمّل مصر إلى ملف سوريا ثم يعيد تحميل سوريا فوقها — هدر 177MB كل دورة wget -q --show-progress -O "${DATA_DIR}/syria-latest.osm.pbf.new" \ "https://download.geofabrik.de/asia/syria-latest.osm.pbf" +wget -q --show-progress -O "${DATA_DIR}/egypt-latest.osm.pbf.new" \ + "https://download.geofabrik.de/africa/egypt-latest.osm.pbf" +wget -q --show-progress -O "${DATA_DIR}/iraq-latest.osm.pbf.new" \ + "https://download.geofabrik.de/asia/iraq-latest.osm.pbf" mv "${DATA_DIR}/jordan-latest.osm.pbf.new" "${DATA_DIR}/jordan-latest.osm.pbf" mv "${DATA_DIR}/syria-latest.osm.pbf.new" "${DATA_DIR}/syria-latest.osm.pbf" mv "${DATA_DIR}/egypt-latest.osm.pbf.new" "${DATA_DIR}/egypt-latest.osm.pbf" +mv "${DATA_DIR}/iraq-latest.osm.pbf.new" "${DATA_DIR}/iraq-latest.osm.pbf" echo "✅ Downloads complete." -# ── Step 2: Import all three countries into PostGIS ─────────────────────── +# ── Step 2: Import all four countries into PostGIS ───────────────────────── echo "💾 Importing Jordan into PostGIS (--create resets planet_osm_* tables)..." docker compose --profile import run --rm osm-import \ osm2pgsql --create --slim --cache 1000 \ @@ -61,13 +62,30 @@ docker compose --profile import run --rm osm-import \ --database mapdb --host db --user mapuser \ /data/egypt-latest.osm.pbf -# ── Step 3: Merge all three PBFs into master_map.osm.pbf ───────────────── +echo "💾 Importing Iraq into PostGIS (--append)..." +docker compose --profile import run --rm osm-import \ + osm2pgsql --append --slim --cache 1000 \ + --database mapdb --host db --user mapuser \ + /data/iraq-latest.osm.pbf + +# ── Step 2b: إسقاط جداول osm2pgsql الوسيطة ──────────────────────────────── +# planet_osm_nodes/ways/rels ينشئها --slim لدعم --append فقط، ولا يقرأها +# التطبيق ولا martin إطلاقاً (جداول الإخراج هي line/polygon/point/roads). +# قياس 30/07/2026: nodes 5031MB + ways 1400MB ≈ 6.4GB نائمة بين الدورات. +# آمن لأن --create في الخطوة 2 يعيد إنشاءها من الصفر كل تشغيل. +# ⚠️ لا تشغّل osm2pgsql --append بعد هذه النقطة حتى الدورة القادمة. +echo "🧹 إسقاط جداول osm2pgsql الوسيطة (تُعاد في الدورة القادمة)..." +docker compose exec -T db psql -U mapuser -d mapdb -c \ + "DROP TABLE IF EXISTS planet_osm_nodes, planet_osm_ways, planet_osm_rels;" + +# ── Step 3: Merge all four PBFs into master_map.osm.pbf ──────────────────── # ✅ FIX: Old script wrote to region.osm.pbf — GraphHopper reads master_map.osm.pbf -echo "🗺️ Merging Jordan + Syria + Egypt → ${MASTER_FILE}..." +echo "🗺️ Merging Jordan + Syria + Egypt + Iraq → ${MASTER_FILE}..." osmium merge \ "${DATA_DIR}/jordan-latest.osm.pbf" \ "${DATA_DIR}/syria-latest.osm.pbf" \ "${DATA_DIR}/egypt-latest.osm.pbf" \ + "${DATA_DIR}/iraq-latest.osm.pbf" \ -o "${MASTER_FILE}" --overwrite echo "✅ Master PBF built: $(du -sh ${MASTER_FILE} | cut -f1)" @@ -92,16 +110,48 @@ docker compose exec -T db psql -U mapuser -d mapdb -t -c \ echo "✅ Landmark sync done." # ── Step 6: Rebuild GraphHopper routing index ───────────────────────────── +# ذروة البناء لأربع دول ~3GB. على سيرفر بـ7.8GB الهامش ضيق، فنُخلي الذاكرة +# للحاويات غير الضرورية أثناء البناء فقط ثم نعيدها. db و redis يبقيان لأن +# الخطوات التالية تحتاجهما. echo "🚗 Rebuilding GraphHopper routing graph (may take 5-10 min)..." +echo "⏸️ إيقاف مؤقت للحاويات غير الضرورية لتحرير الذاكرة أثناء البناء..." +docker compose stop api web dashboard martin || true +# نُعيدها مهما كانت نتيجة البناء — بما في ذلك الخروج المبكر عند الفشل +trap 'echo "▶️ إعادة تشغيل الحاويات المؤقتة..."; docker compose up -d api web dashboard martin || true' EXIT + docker compose stop routing rm -rf "${DATA_DIR}/graph-cache" "${DATA_DIR}/default-gh" docker compose up -d routing echo "✅ GraphHopper restarting from ${MASTER_FILE}." +# ── Step 6b: بوابة تحقق — اتجاهات السير ────────────────────────────────── +# انحدار 26/07/2026: سقوط قاعدة "!car_access → 0" جعل المحرك يتجاهل كل oneway +# وكل دوار لأيام دون أن يلاحظ أحد. لا نُنهي التحديث دون إثبات أنها تعمل. +# التفاصيل: docs/ROUTING_ONEWAY_AR.md +echo "⏳ انتظار جهوزية المحرك قبل فحص الاتجاهات..." +GH_READY=0 +for _ in $(seq 1 90); do + if curl -fsS http://localhost:8989/health >/dev/null 2>&1; then GH_READY=1; break; fi + sleep 10 +done + +if [ "$GH_READY" = "0" ]; then + echo "❌ المحرك لم يجهز خلال 15 دقيقة — راجع: docker compose logs -f routing" + echo " (السبب المتكرر: exited with code 137 أي نفاد ذاكرة أثناء البناء)" + exit 1 +fi + +if ! APP_DIR="${APP_DIR}" bash "${APP_DIR}/infrastructure/scripts/verify-oneway.sh"; then + echo "" + echo "❌❌ المحرك يتجاهل اتجاهات السير — التوجيه غير صالح للاستخدام." + echo " اقرأ: docs/ROUTING_ONEWAY_AR.md" + exit 1 +fi + # ── Step 7: Cache flush ─────────────────────────────────────────────────── echo "🧹 Flushing Redis traffic cache..." docker compose exec -T redis redis-cli flushall echo "" -echo "✅ 10-day update complete — Jordan + Syria + Egypt + approved delta applied." +echo "✅ 10-day update complete — Jordan + Syria + Egypt + Iraq + approved delta applied." echo " GraphHopper is rebuilding in the background. Allow 5-10 min for routing to be ready." diff --git a/infrastructure/scripts/verify-oneway.sh b/infrastructure/scripts/verify-oneway.sh new file mode 100755 index 0000000..5f121fb --- /dev/null +++ b/infrastructure/scripts/verify-oneway.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# -------------------------------------------------------------------------- +# verify-oneway.sh — يتحقق أن محرك التوجيه يحترم اتجاهات السير +# +# يختار طرقاً موسومة oneway=yes من البيانات نفسها، ويطلب مساراً على كل منها +# في الاتجاهين. على طريق أحادي سليم يجب أن تختلف المسافتان (العودة تحتاج +# التفافاً). التطابق التام = المحرك يتجاهل oneway. +# +# السبب المعتاد للفشل: سقوط قاعدة "!car_access → 0" من custom_model. +# التفاصيل الكاملة: docs/ROUTING_ONEWAY_AR.md +# +# bash infrastructure/scripts/verify-oneway.sh +# +# متغيّرات: GH_URL (افتراضي http://localhost:8989)، BBOX، SAMPLES +# -------------------------------------------------------------------------- +set -uo pipefail + +APP_DIR="${APP_DIR:-/home/hamzadoctor/app}" +GH_URL="${GH_URL:-http://localhost:8989}" +PBF="${PBF:-${APP_DIR}/infrastructure/osm-data/master_map.osm.pbf}" +BBOX="${BBOX:-36.055,32.100,36.072,32.116}" # lon1,lat1,lon2,lat2 +SAMPLES="${SAMPLES:-8}" + +command -v osmium >/dev/null || { echo "❌ osmium غير مثبّت: apt-get install -y osmium-tool"; exit 2; } +[ -f "$PBF" ] || { echo "❌ الملف غير موجود: $PBF"; exit 2; } +curl -fsS "$GH_URL/health" >/dev/null || { echo "❌ المحرك لا يستجيب على $GH_URL"; exit 2; } + +TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT + +osmium extract -b "$BBOX" "$PBF" -o "$TMP/area.pbf" --overwrite >/dev/null 2>&1 +osmium tags-filter "$TMP/area.pbf" w/oneway=yes -o "$TMP/ow.pbf" --overwrite >/dev/null 2>&1 +osmium export -f geojson "$TMP/ow.pbf" -o "$TMP/ow.geojson" --overwrite >/dev/null 2>&1 + +GH_URL="$GH_URL" SAMPLES="$SAMPLES" python3 - "$TMP/ow.geojson" <<'PY' +import json, os, sys, urllib.request + +gh = os.environ["GH_URL"] +want = int(os.environ["SAMPLES"]) + +def route(a, b): + u = f"{gh}/route?point={a}&point={b}&profile=car" + try: + return round(json.load(urllib.request.urlopen(u, timeout=20))["paths"][0]["distance"]) + except Exception: + return None + +feats = json.load(open(sys.argv[1]))["features"] +print("═" * 60) +print(" فحص احترام اتجاهات السير — " + gh) +print("═" * 60) + +tested = identical = 0 +for f in feats: + if tested >= want: + break + g = f["geometry"] + if g["type"] != "LineString" or len(g["coordinates"]) < 2: + continue + c = g["coordinates"] + a = f"{c[0][1]:.7f},{c[0][0]:.7f}" + b = f"{c[-1][1]:.7f},{c[-1][0]:.7f}" + fwd, rev = route(a, b), route(b, a) + if fwd is None or rev is None or fwd == 0: + continue + tested += 1 + name = (f["properties"].get("name") or "بلا اسم")[:24] + # المعيار هو الاختلاف لا نسبته: الالتفاف قد يكون صغيراً نسبياً على مقطع طويل + ok = fwd != rev + identical += (not ok) + print(f" {name:24} ذهاب:{fwd:6d}م عودة:{rev:6d}م {'✅' if ok else '❌ متطابق'}") + +print() +if tested == 0: + print("⚠️ لم يُعثر على طرق أحادية صالحة للاختبار — راجع BBOX أو البيانات.") + sys.exit(2) +if identical: + print(f"❌ فشل: {identical} من {tested} طريقاً أحادياً يُخترق — المحرك يتجاهل oneway.") + print(" الأرجح: سقوط قاعدة '!car_access → 0' من custom_model في config.yml") + print(" اقرأ: docs/ROUTING_ONEWAY_AR.md") + sys.exit(1) +print(f"✅ نجح: {tested} طريقاً أحادياً، كلها محترمة الاتجاه.") +PY diff --git a/packages/flutter-sdk/.dart_tool/package_graph.json b/packages/flutter-sdk/.dart_tool/package_graph.json index 5248373..00369c4 100644 --- a/packages/flutter-sdk/.dart_tool/package_graph.json +++ b/packages/flutter-sdk/.dart_tool/package_graph.json @@ -5,7 +5,7 @@ "packages": [ { "name": "intaleq_maps", - "version": "2.2.1", + "version": "2.3.0", "dependencies": [ "flutter", "http", @@ -223,6 +223,17 @@ "meta" ] }, + { + "name": "leak_tracker", + "version": "11.0.2", + "dependencies": [ + "clock", + "collection", + "meta", + "path", + "vm_service" + ] + }, { "name": "term_glyph", "version": "1.2.2", @@ -275,6 +286,11 @@ "flutter" ] }, + { + "name": "vm_service", + "version": "15.0.0", + "dependencies": [] + }, { "name": "xml", "version": "6.6.1", @@ -313,22 +329,6 @@ "name": "ffi", "version": "2.2.0", "dependencies": [] - }, - { - "name": "leak_tracker", - "version": "11.0.2", - "dependencies": [ - "clock", - "collection", - "meta", - "path", - "vm_service" - ] - }, - { - "name": "vm_service", - "version": "15.0.0", - "dependencies": [] } ], "configVersion": 1 diff --git a/packages/flutter-sdk/.gitignore b/packages/flutter-sdk/.gitignore new file mode 100644 index 0000000..a7938b6 --- /dev/null +++ b/packages/flutter-sdk/.gitignore @@ -0,0 +1,5 @@ +.dart_tool/ +example/.dart_tool/ +example/pubspec.lock +.flutter-plugins-dependencies +example/.flutter-plugins-dependencies diff --git a/packages/flutter-sdk/CHANGELOG.md b/packages/flutter-sdk/CHANGELOG.md index de29eba..a6c6779 100644 --- a/packages/flutter-sdk/CHANGELOG.md +++ b/packages/flutter-sdk/CHANGELOG.md @@ -1,3 +1,19 @@ +## 2.3.0 + +* Refreshed bundled `assets/style.json` (offline fallback) to match the live server style: + slate-on-sand road palette, per-class zoom thresholds (fixes low-zoom street clutter), + building layer ordering fix (labels/icons no longer hidden under building fills), + bridge rendering, seasonal/intermittent waterway styling, expanded POI categories + (shop, bank, fuel, hotel, police), and OSM-Carto-style landuse tinting + (retail/farmland/forest/grass/power). +* Removed the broken `demotiles.maplibre.org` sprite reference from the bundled style — + apps must now supply POI icons via `onStyleLoaded` + `styleimagemissing` (see + `apps/web/src/utils/mapIcons.ts` in the monorepo for the reference implementation). + Without this, icon-based layers (POIs, direction arrows) render with empty icons. +* Note: this only updates the **local/offline** style asset. The primary runtime path + (`IntaleqStyles.light/obsidian`) already served the corrected style live via + `/api/maps/style.json` as of 2026-07-31 — no server change needed for those. + ## 2.2.1 * Fixed Polyline and Marker comparison/equality checks. diff --git a/packages/flutter-sdk/assets/style.json b/packages/flutter-sdk/assets/style.json index 7aea78e..9d0560a 100644 --- a/packages/flutter-sdk/assets/style.json +++ b/packages/flutter-sdk/assets/style.json @@ -1,10 +1,10 @@ { "version": 8, - "name": "Intaleq Premium — Light v2.9", + "name": "Intaleq Premium Map Style", "metadata": { "brand": "Intaleq", - "version": "2.10.0-light", - "description": "Intaleq light theme — buildings at close zoom only, on-road labels, improved arrows" + "version": "2.0.0", + "description": "Google + OSM hybrid style with 3D buildings, railways, subway, waterways, and Intaleq brand palette" }, "center": [ 36.276008, @@ -12,7 +12,6 @@ ], "zoom": 15, "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", @@ -62,7 +61,7 @@ "tiles": [ "https://tiles.intaleqapp.com/overture_building/{z}/{x}/{y}" ], - "maxzoom": 14 + "maxzoom": 16 }, "overture_segments": { "type": "vector", @@ -78,6 +77,13 @@ ], "minzoom": 8, "maxzoom": 18 + }, + "places_iraq": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/places_iraq/{z}/{x}/{y}" + ], + "maxzoom": 14 } }, "layers": [ @@ -85,7 +91,7 @@ "id": "background", "type": "background", "paint": { - "background-color": "#EEF2F7" + "background-color": "#F6F4F0" } }, { @@ -99,8 +105,8 @@ "residential" ], "paint": { - "fill-color": "#F0F4F8", - "fill-opacity": 1.0 + "fill-color": "#F2EFE9", + "fill-opacity": 1 } }, { @@ -114,38 +120,8 @@ "commercial" ], "paint": { - "fill-color": "#FAF5EE", - "fill-opacity": 1.0 - } - }, - { - "id": "landuse-cemetery", - "type": "fill", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "filter": [ - "==", - "landuse", - "cemetery" - ], - "paint": { - "fill-color": "#B8D4BA", - "fill-opacity": 1.0 - } - }, - { - "id": "landuse-military", - "type": "fill", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "filter": [ - "==", - "landuse", - "military" - ], - "paint": { - "fill-color": "#E2D9CC", - "fill-opacity": 1.0 + "fill-color": "#F4EFE6", + "fill-opacity": 1 } }, { @@ -160,10 +136,142 @@ "railway" ], "paint": { - "fill-color": "#E4E8EE", + "fill-color": "#EBE8E2", "fill-opacity": 1 } }, + { + "id": "landuse-retail", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "retail" + ], + "paint": { + "fill-color": "#F5D8D3", + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-farmland", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "farmland", + "farmyard", + "orchard" + ], + "paint": { + "fill-color": "#EFEACB", + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-forest", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "forest", + "natural" + ], + "paint": { + "fill-color": [ + "match", + [ + "get", + "natural" + ], + "wood", + "#B7D6A0", + "#C7E0B4" + ], + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-grass", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "grass", + "meadow" + ], + "paint": { + "fill-color": "#D3E8B8", + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-power", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "==", + "power", + "plant" + ], + [ + "==", + "power", + "substation" + ], + [ + "==", + "landuse", + "industrial" + ] + ], + "paint": { + "fill-color": "#DCC9E8", + "fill-opacity": 0.85, + "fill-outline-color": "#C7ADD9" + } + }, + { + "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 + } + }, { "id": "park-layer", "type": "fill", @@ -224,9 +332,9 @@ "nature_reserve" ], "paint": { - "line-color": "#9ED4A0", + "line-color": "#94D4A0", "line-width": 0.8, - "line-opacity": 0.6 + "line-opacity": 0.7 } }, { @@ -261,7 +369,7 @@ ] ], "paint": { - "fill-color": "#9ECFE8", + "fill-color": "#A9D5E8", "fill-opacity": 0.95 } }, @@ -297,6 +405,59 @@ "line-opacity": 0.8 } }, + { + "id": "waterway-intermittent", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 11, + "filter": [ + "all", + [ + "in", + "waterway", + "river", + "canal", + "stream", + "drain", + "ditch" + ], + [ + "any", + [ + "==", + "intermittent", + "yes" + ], + [ + "==", + "seasonal", + "yes" + ] + ] + ], + "paint": { + "line-color": "#A8CFE0", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 11, + 0.6, + 16, + 3.5 + ], + "line-dasharray": [ + 3, + 2 + ] + } + }, { "id": "waterway-river", "type": "line", @@ -305,9 +466,53 @@ "filter": [ "all", [ - "in", + "==", + "waterway", + "river" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "!=", + "seasonal", + "yes" + ] + ], + "paint": { + "line-color": "#6BB8D8", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 7, + 0.8, + 14, + 4, + 16, + 7 + ], + "line-opacity": 0.95 + } + }, + { + "id": "waterway-canal", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 11, + "filter": [ + "all", + [ + "==", "waterway", - "river", "canal" ], [ @@ -319,31 +524,24 @@ "!=", "seasonal", "yes" - ], - [ - "!=", - "tunnel", - "yes" ] ], "paint": { - "line-color": "#6BB8D8", + "line-color": "#8FC8DE", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 10, - 1.5, - 14, - 4, + 11, + 0.8, 16, - 7 - ], - "line-opacity": 0.95 + 5 + ] } }, { @@ -616,11 +814,12 @@ ], "minzoom": 14, "paint": { - "line-color": "#D4D8DF", + "line-color": "#C8CDD6", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" @@ -651,22 +850,28 @@ "pedestrian" ], "paint": { - "line-color": "#D4D8DF", + "line-color": "#D6DBE1", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 12, - 1.5, + 12.5, + 1.6, + 15, + 4.5, 16, - 10 + 10, + 18, + 15 ], "line-opacity": 0.7 - } + }, + "minzoom": 12.5 }, { "id": "road-core-minor", @@ -687,17 +892,23 @@ "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 12, - 0.8, + 12.5, + 1.0, + 15, + 3.2, 16, - 8 + 8, + 18, + 12 ] - } + }, + "minzoom": 12.5 }, { "id": "approved-road-casing", @@ -709,7 +920,7 @@ "line-join": "round" }, "paint": { - "line-color": "#D4D8DF", + "line-color": "#B9C2CE", "line-width": [ "interpolate", [ @@ -719,11 +930,11 @@ "zoom" ], 12, - 1.5, + 2.2, 16, 10 ], - "line-opacity": 0.7 + "line-opacity": 0.9 } }, { @@ -764,22 +975,28 @@ "tertiary_link" ], "paint": { - "line-color": "#C9CED8", + "line-color": "#BCC7D2", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 2, + 11.5, + 1.4, + 14, + 3.4, 16, - 14 + 14, + 18, + 18 ], "line-opacity": 0.75 - } + }, + "minzoom": 11.5 }, { "id": "road-core-tertiary", @@ -793,21 +1010,27 @@ "tertiary_link" ], "paint": { - "line-color": "#FFFFFF", + "line-color": "#DCE5EC", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 1.2, + 11.5, + 0.9, + 14, + 2.4, 16, - 11 + 11, + 18, + 14 ] - } + }, + "minzoom": 11.5 }, { "id": "road-casing-secondary", @@ -821,22 +1044,28 @@ "secondary_link" ], "paint": { - "line-color": "#C4CFDE", + "line-color": "#98AABC", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 2.5, + 10, + 1.4, + 13, + 3.2, 16, - 16 + 16, + 18, + 21 ], "line-opacity": 0.8 - } + }, + "minzoom": 10 }, { "id": "road-core-secondary", @@ -850,21 +1079,27 @@ "secondary_link" ], "paint": { - "line-color": "#F8FBFF", + "line-color": "#BACAD8", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 1.8, + 10, + 0.9, + 13, + 2.2, 16, - 13 + 13, + 18, + 17 ] - } + }, + "minzoom": 10 }, { "id": "road-casing-primary", @@ -878,22 +1113,28 @@ "primary_link" ], "paint": { - "line-color": "#C8B868", + "line-color": "#71889E", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 10, - 3, + 8, + 1.4, + 12, + 3.4, 16, - 18 + 18, + 18, + 24 ], "line-opacity": 0.7 - } + }, + "minzoom": 8 }, { "id": "road-core-primary", @@ -907,8 +1148,128 @@ "primary_link" ], "paint": { - "line-color": "#EDD870", + "line-color": "#93A9BC", "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 8, + 0.9, + 12, + 2.4, + 16, + 14, + 18, + 19 + ] + }, + "minzoom": 8 + }, + { + "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" + ], + "paint": { + "line-color": "#4E6478", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 5, + 1.4, + 10, + 3.2, + 16, + 20, + 18, + 26 + ], + "line-opacity": 0.75 + }, + "minzoom": 5 + }, + { + "id": "road-core-motorway-trunk", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "motorway", + "motorway_link", + "trunk", + "trunk_link" + ], + "paint": { + "line-color": "#6B8299", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 5, + 0.8, + 10, + 2.2, + 16, + 16, + 18, + 21 + ] + }, + "minzoom": 5 + }, + { + "id": "building-fill-flat", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "has", + "building" + ], + "paint": { + "fill-color": "#E4DED4", + "fill-opacity": 1, + "fill-outline-color": "#C8C0B2" + } + }, + { + "id": "building-3d", + "type": "fill-extrusion", + "source": "overture_buildings", + "source-layer": "overture_building", + "minzoom": 16, + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-extrusion-color": "#DDD8D0", + "fill-extrusion-height": [ "interpolate", [ "linear" @@ -916,71 +1277,212 @@ [ "zoom" ], - 10, - 2, + 14, + [ + "*", + [ + "coalesce", + [ + "to-number", + [ + "get", + "num_floors" + ], + null + ], + 3 + ], + 2.5 + ], + 17, + [ + "coalesce", + [ + "to-number", + [ + "get", + "height" + ], + null + ], + [ + "*", + [ + "coalesce", + [ + "to-number", + [ + "get", + "num_floors" + ], + null + ], + 3 + ], + 3.5 + ], + 12 + ] + ], + "fill-extrusion-base": 0, + "fill-extrusion-opacity": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 14, + 0.55, 16, - 14 + 0.85 + ], + "fill-extrusion-vertical-gradient": true + } + }, + { + "id": "overture-building-footprint", + "type": "fill", + "source": "overture_buildings", + "source-layer": "overture_building", + "paint": { + "fill-color": "#E4DED4", + "fill-opacity": 1, + "fill-outline-color": "#C8C0B2" + }, + "minzoom": 14 + }, + { + "id": "bridge-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 12, + "filter": [ + "all", + [ + "==", + "bridge", + "yes" + ], + [ + "in", + "highway", + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "residential", + "unclassified", + "living_street", + "motorway_link", + "trunk_link", + "primary_link", + "secondary_link" + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "round" + }, + "paint": { + "line-color": "#5A6B7C", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 12, + 5, + 16, + 22, + 18, + 30 ] } }, { - "id": "road-casing-motorway", + "id": "bridge-core", "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", + "minzoom": 12, "filter": [ - "in", - "highway", - "motorway", - "motorway_link", - "trunk", - "trunk_link" - ], - "paint": { - "line-color": "#C8A84B", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 9, - 4, - 16, - 20 + "all", + [ + "==", + "bridge", + "yes" ], - "line-opacity": 0.75 - } - }, - { - "id": "road-core-motorway", - "type": "line", - "source": "local-osm-lines", - "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "motorway", - "motorway_link", - "trunk", - "trunk_link" + [ + "in", + "highway", + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "residential", + "unclassified", + "living_street", + "motorway_link", + "trunk_link", + "primary_link", + "secondary_link" + ] ], + "layout": { + "line-cap": "butt", + "line-join": "round" + }, "paint": { - "line-color": "#F0C040", + "line-color": [ + "match", + [ + "get", + "highway" + ], + "motorway", + "#6B8299", + "trunk", + "#6B8299", + "motorway_link", + "#6B8299", + "trunk_link", + "#6B8299", + "primary", + "#93A9BC", + "primary_link", + "#93A9BC", + "secondary", + "#BACAD8", + "secondary_link", + "#BACAD8", + "tertiary", + "#DCE5EC", + "#FFFFFF" + ], "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 9, - 2.5, + 12, + 3.5, 16, - 16 + 18, + 18, + 25 ] } }, @@ -1012,12 +1514,9 @@ ], "layout": { "symbol-placement": "line", - "symbol-spacing": 120, - "text-field": "→", - "text-font": [ - "Noto Sans Regular" - ], - "text-size": [ + "symbol-spacing": 90, + "icon-image": "arrow", + "icon-size": [ "interpolate", [ "linear" @@ -1026,223 +1525,16 @@ "zoom" ], 15, - 16, - 17, - 22, - 19, - 28 + 0.35, + 18, + 0.55 ], - "text-keep-upright": false, - "text-rotation-alignment": "map", - "text-pitch-alignment": "viewport", - "text-allow-overlap": true, - "text-ignore-placement": true, - "text-padding": 0, - "text-letter-spacing": -0.1 + "icon-rotation-alignment": "map", + "icon-allow-overlap": true, + "icon-ignore-placement": true }, "paint": { - "text-color": "rgba(80,96,120,0.55)", - "text-halo-color": "rgba(255,255,255,0.25)", - "text-halo-width": 1 - } - }, - { - "id": "building-fill-flat", - "type": "fill", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "filter": [ - "has", - "building" - ], - "minzoom": 15, - "maxzoom": 16, - "paint": { - "fill-color": "#DDD8D0", - "fill-opacity": 0.85, - "fill-outline-color": "#C4BEB4" - } - }, - { - "id": "building-3d", - "type": "fill-extrusion", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "minzoom": 16, - "filter": [ - "has", - "building" - ], - "paint": { - "fill-extrusion-color": "#DDD8D0", - "fill-extrusion-height": [ - "coalesce", - [ - "to-number", - [ - "get", - "height" - ] - ], - [ - "*", - [ - "to-number", - [ - "get", - "building:levels" - ], - 2 - ], - 3.5 - ], - 7 - ], - "fill-extrusion-base": [ - "coalesce", - [ - "to-number", - [ - "get", - "min_height" - ] - ], - 0 - ], - "fill-extrusion-opacity": 0.8, - "fill-extrusion-vertical-gradient": true - } - }, - { - "id": "building-3d-overture", - "type": "fill-extrusion", - "source": "overture_buildings", - "source-layer": "overture_building", - "minzoom": 16, - "paint": { - "fill-extrusion-color": [ - "match", - [ - "get", - "subtype" - ], - "commercial", - "#D8D0C0", - "retail", - "#E0D0B8", - "industrial", - "#C8D0DC", - "religious", - "#C8D4EC", - "education", - "#D4E0C4", - "medical", - "#E8D4D4", - "#DDD8D0" - ], - "fill-extrusion-height": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 16, - [ - "coalesce", - [ - "to-number", - [ - "get", - "height" - ] - ], - [ - "*", - [ - "coalesce", - [ - "to-number", - [ - "get", - "num_floors" - ] - ], - 3 - ], - 3.5 - ], - 9 - ], - 18, - [ - "coalesce", - [ - "to-number", - [ - "get", - "height" - ] - ], - [ - "*", - [ - "coalesce", - [ - "to-number", - [ - "get", - "num_floors" - ] - ], - 3 - ], - 3.5 - ], - 12 - ] - ], - "fill-extrusion-base": [ - "coalesce", - [ - "to-number", - [ - "get", - "min_height" - ] - ], - 0 - ], - "fill-extrusion-opacity": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 15, - 0, - 16, - 0.6, - 17, - 0.88 - ], - "fill-extrusion-vertical-gradient": true - } - }, - { - "id": "overture-building-footprint", - "type": "fill", - "source": "overture_buildings", - "source-layer": "overture_building", - "minzoom": 15, - "maxzoom": 16, - "paint": { - "fill-color": "#DDD8D0", - "fill-opacity": 0.82, - "fill-outline-color": "#C4BEB4" + "icon-opacity": 0.75 } }, { @@ -1295,7 +1587,7 @@ "#7722AA", "#4A5568" ], - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.9)", "text-halo-width": 2 } }, @@ -1345,7 +1637,7 @@ }, "paint": { "text-color": "#2E86AB", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.85)", "text-halo-width": 2 } }, @@ -1365,11 +1657,12 @@ "Noto Sans Regular" ], "text-size": 10, - "text-allow-overlap": false + "text-allow-overlap": false, + "text-ignore-placement": false }, "paint": { "text-color": "#5A5048", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.95)", "text-halo-width": 1.5 } }, @@ -1393,40 +1686,7 @@ }, "paint": { "text-color": "#5A5048", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "overture-building-names", - "type": "symbol", - "source": "overture_buildings", - "source-layer": "overture_building", - "minzoom": 17, - "filter": [ - "has", - "names" - ], - "layout": { - "text-field": [ - "coalesce", - [ - "get", - "names" - ], - "" - ], - "text-font": [ - "Noto Sans Regular" - ], - "text-size": 10, - "text-anchor": "center", - "text-allow-overlap": false, - "text-max-width": 8 - }, - "paint": { - "text-color": "#5A5048", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.95)", "text-halo-width": 1.5 } }, @@ -1443,7 +1703,7 @@ "unclassified", "living_street" ], - "minzoom": 16, + "minzoom": 15, "layout": { "text-field": [ "coalesce", @@ -1468,24 +1728,22 @@ [ "zoom" ], - 16, + 15, 10, 18, 13 ], "symbol-placement": "line", - "text-rotation-alignment": "map", - "text-pitch-alignment": "viewport", - "text-keep-upright": true, - "text-max-angle": 25, + "text-letter-spacing": 0.05, + "text-padding": 15, "symbol-spacing": 300, - "text-letter-spacing": 0.04, - "text-padding": 8, - "text-allow-overlap": false + "text-max-angle": 30, + "text-allow-overlap": false, + "text-ignore-placement": false }, "paint": { "text-color": "#4A5568", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.92)", "text-halo-width": 1.8 } }, @@ -1536,74 +1794,19 @@ 16 ], "symbol-placement": "line", - "text-rotation-alignment": "map", - "text-pitch-alignment": "viewport", - "text-keep-upright": true, - "text-max-angle": 22, + "text-letter-spacing": 0.06, + "text-padding": 20, "symbol-spacing": 350, - "text-letter-spacing": 0.05, - "text-padding": 12, - "text-allow-overlap": false + "text-max-angle": 25, + "text-allow-overlap": false, + "text-ignore-placement": false }, "paint": { "text-color": "#1A2332", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.95)", "text-halo-width": 2.5 } }, - { - "id": "overture-street-names", - "type": "symbol", - "source": "overture_segments", - "source-layer": "overture_segment", - "minzoom": 14, - "filter": [ - "has", - "names" - ], - "layout": { - "text-field": [ - "coalesce", - [ - "get", - "names" - ], - "" - ], - "text-font": [ - "Noto Sans Regular" - ], - "text-size": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 14, - 9, - 16, - 12, - 18, - 14 - ], - "symbol-placement": "line", - "text-rotation-alignment": "map", - "text-pitch-alignment": "viewport", - "text-keep-upright": true, - "text-max-angle": 28, - "symbol-spacing": 280, - "text-letter-spacing": 0.04, - "text-padding": 15, - "text-allow-overlap": false - }, - "paint": { - "text-color": "#4A5568", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 2 - } - }, { "id": "poi-hospital", "type": "symbol", @@ -1617,7 +1820,7 @@ ], "layout": { "icon-image": "hospital", - "icon-size": 1.0, + "icon-size": 1, "text-field": [ "coalesce", [ @@ -1643,7 +1846,7 @@ }, "paint": { "text-color": "#C0392B", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "white", "text-halo-width": 2 } }, @@ -1676,18 +1879,17 @@ "text-font": [ "Noto Sans Regular" ], - "text-size": 11, + "text-size": 10, "text-offset": [ 0, 1.2 ], - "text-anchor": "top", - "text-allow-overlap": false + "text-anchor": "top" }, "paint": { "text-color": "#1A7A3C", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 2 + "text-halo-color": "white", + "text-halo-width": 1.5 } }, { @@ -1724,57 +1926,11 @@ 0, 1.2 ], - "text-anchor": "top", - "text-allow-overlap": false + "text-anchor": "top" }, "paint": { "text-color": "#1A6B3A", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 2 - } - }, - { - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#5A4A8A", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "white", "text-halo-width": 2 } }, @@ -1827,10 +1983,54 @@ }, "paint": { "text-color": "#3D4A5C", - "text-halo-color": "rgba(255,255,255,0.93)", + "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", @@ -1893,10 +2093,234 @@ }, "paint": { "text-color": "#CC2233", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.95)", "text-halo-width": 2 } }, + { + "id": "poi-shop", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "all", + [ + "has", + "shop" + ], + [ + "!in", + "shop", + "vacant", + "no" + ] + ], + "layout": { + "icon-image": "shop", + "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": "#7B3FA0", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-police", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 15, + "filter": [ + "in", + "amenity", + "police", + "fire_station" + ], + "layout": { + "icon-image": "police", + "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": "#2F5AA8", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-bank", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "in", + "amenity", + "bank", + "bureau_de_change", + "atm" + ], + "layout": { + "icon-image": "shop", + "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": "#2E6B4F", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-fuel", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 14, + "filter": [ + "==", + "amenity", + "fuel" + ], + "layout": { + "icon-image": "shop", + "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": "#B5651D", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-hotel", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "in", + "tourism", + "hotel", + "motel", + "guest_house", + "hostel" + ], + "layout": { + "icon-image": "tourist", + "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": "#8A5A8A", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, { "id": "place-labels-area", "type": "symbol", @@ -1937,11 +2361,12 @@ 13 ], "text-padding": 8, - "text-allow-overlap": false + "text-allow-overlap": false, + "text-ignore-placement": false }, "paint": { "text-color": "#34495E", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.85)", "text-halo-width": 2 } }, @@ -2052,13 +2477,15 @@ "#1A2740", "town", "#2C3E50", + "village", + "#3D4F62", "suburb", - "#34495E", + "#4A5568", "neighbourhood", - "#34495E", - "#34495E" + "#556677", + "#607080" ], - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.92)", "text-halo-width": [ "match", [ @@ -2079,11 +2506,6 @@ "source": "places_egypt", "source-layer": "places_egypt", "minzoom": 12, - "filter": [ - "!in", - "category", - "street" - ], "layout": { "text-field": [ "coalesce", @@ -2123,7 +2545,7 @@ }, "paint": { "text-color": "#2D3748", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255, 255, 255, 0.9)", "text-halo-width": 2 } }, @@ -2177,7 +2599,7 @@ }, "paint": { "text-color": "#2D3748", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255, 255, 255, 0.9)", "text-halo-width": 2 } }, @@ -2231,9 +2653,150 @@ }, "paint": { "text-color": "#2D3748", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255, 255, 255, 0.9)", "text-halo-width": 2 } + }, + { + "id": "places-iraq-labels", + "type": "symbol", + "source": "places_iraq", + "source-layer": "places_iraq", + "minzoom": 10, + "filter": [ + "!in", + "category", + "street" + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name_ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Bold" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 12, + 9, + 16, + 13 + ], + "text-offset": [ + 0, + 1.5 + ], + "text-anchor": "top", + "text-padding": 8, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#2D3748", + "text-halo-color": "rgba(255, 255, 255, 0.9)", + "text-halo-width": 2 + } + }, + { + "id": "overture-building-names", + "type": "symbol", + "source": "overture_buildings", + "source-layer": "overture_building", + "minzoom": 16, + "filter": [ + "has", + "name_primary" + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name_primary" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-anchor": "center", + "text-allow-overlap": false, + "text-max-width": 8 + }, + "paint": { + "text-color": "#5a5248", + "text-halo-color": "rgba(255, 255, 255, 0.9)", + "text-halo-width": 1.5 + } + }, + { + "id": "overture-street-names", + "type": "symbol", + "source": "overture_segments", + "source-layer": "overture_segment", + "minzoom": 14, + "filter": [ + "has", + "name_primary" + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name_primary" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 14, + 9, + 16, + 12, + 18, + 14 + ], + "symbol-placement": "line", + "text-rotation-alignment": "map", + "text-pitch-alignment": "viewport", + "text-keep-upright": true, + "text-padding": 20, + "text-letter-spacing": 0.04, + "text-max-angle": 30, + "symbol-spacing": 300, + "text-allow-overlap": false, + "text-ignore-placement": false + }, + "paint": { + "text-color": "#4A5568", + "text-halo-color": "rgba(255, 255, 255, 0.95)", + "text-halo-width": 2.2 + } } ] -} +} \ No newline at end of file diff --git a/packages/flutter-sdk/pubspec.yaml b/packages/flutter-sdk/pubspec.yaml index 5caaaae..180a326 100644 --- a/packages/flutter-sdk/pubspec.yaml +++ b/packages/flutter-sdk/pubspec.yaml @@ -2,7 +2,7 @@ name: intaleq_maps description: > Premium Flutter SDK for the Intaleq Map Platform (Jordan & Syria). A drop-in Google Maps Flutter replacement backed by MapLibre GL. -version: 2.2.1 +version: 2.3.0 homepage: https://intaleqapp.com repository: https://github.com/Hamza-Ayed/intaleq_maps issue_tracker: https://github.com/Hamza-Ayed/intaleq_maps/issues diff --git a/style.json b/style.json index 185b76d..9d0560a 100644 --- a/style.json +++ b/style.json @@ -1,10 +1,10 @@ { "version": 8, - "name": "Intaleq Premium — Light v2.9", + "name": "Intaleq Premium Map Style", "metadata": { "brand": "Intaleq", - "version": "2.10.0-light", - "description": "Intaleq light theme — buildings at close zoom only, on-road labels, improved arrows" + "version": "2.0.0", + "description": "Google + OSM hybrid style with 3D buildings, railways, subway, waterways, and Intaleq brand palette" }, "center": [ 36.276008, @@ -12,7 +12,6 @@ ], "zoom": 15, "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", @@ -62,7 +61,7 @@ "tiles": [ "https://tiles.intaleqapp.com/overture_building/{z}/{x}/{y}" ], - "maxzoom": 14 + "maxzoom": 16 }, "overture_segments": { "type": "vector", @@ -78,6 +77,13 @@ ], "minzoom": 8, "maxzoom": 18 + }, + "places_iraq": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/places_iraq/{z}/{x}/{y}" + ], + "maxzoom": 14 } }, "layers": [ @@ -85,7 +91,7 @@ "id": "background", "type": "background", "paint": { - "background-color": "#EEF2F7" + "background-color": "#F6F4F0" } }, { @@ -99,8 +105,8 @@ "residential" ], "paint": { - "fill-color": "#F0F4F8", - "fill-opacity": 1.0 + "fill-color": "#F2EFE9", + "fill-opacity": 1 } }, { @@ -114,38 +120,8 @@ "commercial" ], "paint": { - "fill-color": "#FAF5EE", - "fill-opacity": 1.0 - } - }, - { - "id": "landuse-cemetery", - "type": "fill", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "filter": [ - "==", - "landuse", - "cemetery" - ], - "paint": { - "fill-color": "#B8D4BA", - "fill-opacity": 1.0 - } - }, - { - "id": "landuse-military", - "type": "fill", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "filter": [ - "==", - "landuse", - "military" - ], - "paint": { - "fill-color": "#E2D9CC", - "fill-opacity": 1.0 + "fill-color": "#F4EFE6", + "fill-opacity": 1 } }, { @@ -160,10 +136,142 @@ "railway" ], "paint": { - "fill-color": "#E4E8EE", + "fill-color": "#EBE8E2", "fill-opacity": 1 } }, + { + "id": "landuse-retail", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "retail" + ], + "paint": { + "fill-color": "#F5D8D3", + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-farmland", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "farmland", + "farmyard", + "orchard" + ], + "paint": { + "fill-color": "#EFEACB", + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-forest", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "forest", + "natural" + ], + "paint": { + "fill-color": [ + "match", + [ + "get", + "natural" + ], + "wood", + "#B7D6A0", + "#C7E0B4" + ], + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-grass", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "grass", + "meadow" + ], + "paint": { + "fill-color": "#D3E8B8", + "fill-opacity": 0.85 + } + }, + { + "id": "landuse-power", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "==", + "power", + "plant" + ], + [ + "==", + "power", + "substation" + ], + [ + "==", + "landuse", + "industrial" + ] + ], + "paint": { + "fill-color": "#DCC9E8", + "fill-opacity": 0.85, + "fill-outline-color": "#C7ADD9" + } + }, + { + "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 + } + }, { "id": "park-layer", "type": "fill", @@ -224,9 +332,9 @@ "nature_reserve" ], "paint": { - "line-color": "#9ED4A0", + "line-color": "#94D4A0", "line-width": 0.8, - "line-opacity": 0.6 + "line-opacity": 0.7 } }, { @@ -261,7 +369,7 @@ ] ], "paint": { - "fill-color": "#9ECFE8", + "fill-color": "#A9D5E8", "fill-opacity": 0.95 } }, @@ -297,6 +405,59 @@ "line-opacity": 0.8 } }, + { + "id": "waterway-intermittent", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 11, + "filter": [ + "all", + [ + "in", + "waterway", + "river", + "canal", + "stream", + "drain", + "ditch" + ], + [ + "any", + [ + "==", + "intermittent", + "yes" + ], + [ + "==", + "seasonal", + "yes" + ] + ] + ], + "paint": { + "line-color": "#A8CFE0", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 11, + 0.6, + 16, + 3.5 + ], + "line-dasharray": [ + 3, + 2 + ] + } + }, { "id": "waterway-river", "type": "line", @@ -305,9 +466,53 @@ "filter": [ "all", [ - "in", + "==", + "waterway", + "river" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "!=", + "seasonal", + "yes" + ] + ], + "paint": { + "line-color": "#6BB8D8", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 7, + 0.8, + 14, + 4, + 16, + 7 + ], + "line-opacity": 0.95 + } + }, + { + "id": "waterway-canal", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 11, + "filter": [ + "all", + [ + "==", "waterway", - "river", "canal" ], [ @@ -319,31 +524,24 @@ "!=", "seasonal", "yes" - ], - [ - "!=", - "tunnel", - "yes" ] ], "paint": { - "line-color": "#6BB8D8", + "line-color": "#8FC8DE", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 10, - 1.5, - 14, - 4, + 11, + 0.8, 16, - 7 - ], - "line-opacity": 0.95 + 5 + ] } }, { @@ -616,11 +814,12 @@ ], "minzoom": 14, "paint": { - "line-color": "#D4D8DF", + "line-color": "#C8CDD6", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" @@ -651,22 +850,28 @@ "pedestrian" ], "paint": { - "line-color": "#D4D8DF", + "line-color": "#D6DBE1", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 12, - 1.5, + 12.5, + 1.6, + 15, + 4.5, 16, - 10 + 10, + 18, + 15 ], "line-opacity": 0.7 - } + }, + "minzoom": 12.5 }, { "id": "road-core-minor", @@ -687,17 +892,23 @@ "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 12, - 0.8, + 12.5, + 1.0, + 15, + 3.2, 16, - 8 + 8, + 18, + 12 ] - } + }, + "minzoom": 12.5 }, { "id": "approved-road-casing", @@ -709,7 +920,7 @@ "line-join": "round" }, "paint": { - "line-color": "#D4D8DF", + "line-color": "#B9C2CE", "line-width": [ "interpolate", [ @@ -719,11 +930,11 @@ "zoom" ], 12, - 1.5, + 2.2, 16, 10 ], - "line-opacity": 0.7 + "line-opacity": 0.9 } }, { @@ -764,22 +975,28 @@ "tertiary_link" ], "paint": { - "line-color": "#C9CED8", + "line-color": "#BCC7D2", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 2, + 11.5, + 1.4, + 14, + 3.4, 16, - 14 + 14, + 18, + 18 ], "line-opacity": 0.75 - } + }, + "minzoom": 11.5 }, { "id": "road-core-tertiary", @@ -793,21 +1010,27 @@ "tertiary_link" ], "paint": { - "line-color": "#FFFFFF", + "line-color": "#DCE5EC", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 1.2, + 11.5, + 0.9, + 14, + 2.4, 16, - 11 + 11, + 18, + 14 ] - } + }, + "minzoom": 11.5 }, { "id": "road-casing-secondary", @@ -821,22 +1044,28 @@ "secondary_link" ], "paint": { - "line-color": "#C4CFDE", + "line-color": "#98AABC", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 2.5, + 10, + 1.4, + 13, + 3.2, 16, - 16 + 16, + 18, + 21 ], "line-opacity": 0.8 - } + }, + "minzoom": 10 }, { "id": "road-core-secondary", @@ -850,21 +1079,27 @@ "secondary_link" ], "paint": { - "line-color": "#F8FBFF", + "line-color": "#BACAD8", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 11, - 1.8, + 10, + 0.9, + 13, + 2.2, 16, - 13 + 13, + 18, + 17 ] - } + }, + "minzoom": 10 }, { "id": "road-casing-primary", @@ -878,22 +1113,28 @@ "primary_link" ], "paint": { - "line-color": "#C8B868", + "line-color": "#71889E", "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 10, - 3, + 8, + 1.4, + 12, + 3.4, 16, - 18 + 18, + 18, + 24 ], "line-opacity": 0.7 - } + }, + "minzoom": 8 }, { "id": "road-core-primary", @@ -907,8 +1148,128 @@ "primary_link" ], "paint": { - "line-color": "#EDD870", + "line-color": "#93A9BC", "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 8, + 0.9, + 12, + 2.4, + 16, + 14, + 18, + 19 + ] + }, + "minzoom": 8 + }, + { + "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" + ], + "paint": { + "line-color": "#4E6478", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 5, + 1.4, + 10, + 3.2, + 16, + 20, + 18, + 26 + ], + "line-opacity": 0.75 + }, + "minzoom": 5 + }, + { + "id": "road-core-motorway-trunk", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "motorway", + "motorway_link", + "trunk", + "trunk_link" + ], + "paint": { + "line-color": "#6B8299", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 5, + 0.8, + 10, + 2.2, + 16, + 16, + 18, + 21 + ] + }, + "minzoom": 5 + }, + { + "id": "building-fill-flat", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "has", + "building" + ], + "paint": { + "fill-color": "#E4DED4", + "fill-opacity": 1, + "fill-outline-color": "#C8C0B2" + } + }, + { + "id": "building-3d", + "type": "fill-extrusion", + "source": "overture_buildings", + "source-layer": "overture_building", + "minzoom": 16, + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-extrusion-color": "#DDD8D0", + "fill-extrusion-height": [ "interpolate", [ "linear" @@ -916,71 +1277,212 @@ [ "zoom" ], - 10, - 2, + 14, + [ + "*", + [ + "coalesce", + [ + "to-number", + [ + "get", + "num_floors" + ], + null + ], + 3 + ], + 2.5 + ], + 17, + [ + "coalesce", + [ + "to-number", + [ + "get", + "height" + ], + null + ], + [ + "*", + [ + "coalesce", + [ + "to-number", + [ + "get", + "num_floors" + ], + null + ], + 3 + ], + 3.5 + ], + 12 + ] + ], + "fill-extrusion-base": 0, + "fill-extrusion-opacity": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 14, + 0.55, 16, - 14 + 0.85 + ], + "fill-extrusion-vertical-gradient": true + } + }, + { + "id": "overture-building-footprint", + "type": "fill", + "source": "overture_buildings", + "source-layer": "overture_building", + "paint": { + "fill-color": "#E4DED4", + "fill-opacity": 1, + "fill-outline-color": "#C8C0B2" + }, + "minzoom": 14 + }, + { + "id": "bridge-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 12, + "filter": [ + "all", + [ + "==", + "bridge", + "yes" + ], + [ + "in", + "highway", + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "residential", + "unclassified", + "living_street", + "motorway_link", + "trunk_link", + "primary_link", + "secondary_link" + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "round" + }, + "paint": { + "line-color": "#5A6B7C", + "line-width": [ + "interpolate", + [ + "exponential", + 1.6 + ], + [ + "zoom" + ], + 12, + 5, + 16, + 22, + 18, + 30 ] } }, { - "id": "road-casing-motorway", + "id": "bridge-core", "type": "line", "source": "local-osm-lines", "source-layer": "planet_osm_line", + "minzoom": 12, "filter": [ - "in", - "highway", - "motorway", - "motorway_link", - "trunk", - "trunk_link" - ], - "paint": { - "line-color": "#C8A84B", - "line-width": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 9, - 4, - 16, - 20 + "all", + [ + "==", + "bridge", + "yes" ], - "line-opacity": 0.75 - } - }, - { - "id": "road-core-motorway", - "type": "line", - "source": "local-osm-lines", - "source-layer": "planet_osm_line", - "filter": [ - "in", - "highway", - "motorway", - "motorway_link", - "trunk", - "trunk_link" + [ + "in", + "highway", + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "residential", + "unclassified", + "living_street", + "motorway_link", + "trunk_link", + "primary_link", + "secondary_link" + ] ], + "layout": { + "line-cap": "butt", + "line-join": "round" + }, "paint": { - "line-color": "#F0C040", + "line-color": [ + "match", + [ + "get", + "highway" + ], + "motorway", + "#6B8299", + "trunk", + "#6B8299", + "motorway_link", + "#6B8299", + "trunk_link", + "#6B8299", + "primary", + "#93A9BC", + "primary_link", + "#93A9BC", + "secondary", + "#BACAD8", + "secondary_link", + "#BACAD8", + "tertiary", + "#DCE5EC", + "#FFFFFF" + ], "line-width": [ "interpolate", [ - "linear" + "exponential", + 1.6 ], [ "zoom" ], - 9, - 2.5, + 12, + 3.5, 16, - 16 + 18, + 18, + 25 ] } }, @@ -1012,12 +1514,9 @@ ], "layout": { "symbol-placement": "line", - "symbol-spacing": 120, - "text-field": "→", - "text-font": [ - "Noto Sans Regular" - ], - "text-size": [ + "symbol-spacing": 90, + "icon-image": "arrow", + "icon-size": [ "interpolate", [ "linear" @@ -1026,223 +1525,16 @@ "zoom" ], 15, - 16, - 17, - 22, - 19, - 28 + 0.35, + 18, + 0.55 ], - "text-keep-upright": false, - "text-rotation-alignment": "map", - "text-pitch-alignment": "viewport", - "text-allow-overlap": true, - "text-ignore-placement": true, - "text-padding": 0, - "text-letter-spacing": -0.1 + "icon-rotation-alignment": "map", + "icon-allow-overlap": true, + "icon-ignore-placement": true }, "paint": { - "text-color": "rgba(80,96,120,0.55)", - "text-halo-color": "rgba(255,255,255,0.25)", - "text-halo-width": 1 - } - }, - { - "id": "building-fill-flat", - "type": "fill", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "filter": [ - "has", - "building" - ], - "minzoom": 15, - "maxzoom": 16, - "paint": { - "fill-color": "#DDD8D0", - "fill-opacity": 0.85, - "fill-outline-color": "#C4BEB4" - } - }, - { - "id": "building-3d", - "type": "fill-extrusion", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", - "minzoom": 16, - "filter": [ - "has", - "building" - ], - "paint": { - "fill-extrusion-color": "#DDD8D0", - "fill-extrusion-height": [ - "coalesce", - [ - "to-number", - [ - "get", - "height" - ] - ], - [ - "*", - [ - "to-number", - [ - "get", - "building:levels" - ], - 2 - ], - 3.5 - ], - 7 - ], - "fill-extrusion-base": [ - "coalesce", - [ - "to-number", - [ - "get", - "min_height" - ] - ], - 0 - ], - "fill-extrusion-opacity": 0.8, - "fill-extrusion-vertical-gradient": true - } - }, - { - "id": "building-3d-overture", - "type": "fill-extrusion", - "source": "overture_buildings", - "source-layer": "overture_building", - "minzoom": 16, - "paint": { - "fill-extrusion-color": [ - "match", - [ - "get", - "subtype" - ], - "commercial", - "#D8D0C0", - "retail", - "#E0D0B8", - "industrial", - "#C8D0DC", - "religious", - "#C8D4EC", - "education", - "#D4E0C4", - "medical", - "#E8D4D4", - "#DDD8D0" - ], - "fill-extrusion-height": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 16, - [ - "coalesce", - [ - "to-number", - [ - "get", - "height" - ] - ], - [ - "*", - [ - "coalesce", - [ - "to-number", - [ - "get", - "num_floors" - ] - ], - 3 - ], - 3.5 - ], - 9 - ], - 18, - [ - "coalesce", - [ - "to-number", - [ - "get", - "height" - ] - ], - [ - "*", - [ - "coalesce", - [ - "to-number", - [ - "get", - "num_floors" - ] - ], - 3 - ], - 3.5 - ], - 12 - ] - ], - "fill-extrusion-base": [ - "coalesce", - [ - "to-number", - [ - "get", - "min_height" - ] - ], - 0 - ], - "fill-extrusion-opacity": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 15, - 0, - 16, - 0.6, - 17, - 0.88 - ], - "fill-extrusion-vertical-gradient": true - } - }, - { - "id": "overture-building-footprint", - "type": "fill", - "source": "overture_buildings", - "source-layer": "overture_building", - "minzoom": 15, - "maxzoom": 16, - "paint": { - "fill-color": "#DDD8D0", - "fill-opacity": 0.82, - "fill-outline-color": "#C4BEB4" + "icon-opacity": 0.75 } }, { @@ -1295,7 +1587,7 @@ "#7722AA", "#4A5568" ], - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.9)", "text-halo-width": 2 } }, @@ -1345,7 +1637,7 @@ }, "paint": { "text-color": "#2E86AB", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.85)", "text-halo-width": 2 } }, @@ -1365,11 +1657,12 @@ "Noto Sans Regular" ], "text-size": 10, - "text-allow-overlap": false + "text-allow-overlap": false, + "text-ignore-placement": false }, "paint": { "text-color": "#5A5048", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.95)", "text-halo-width": 1.5 } }, @@ -1393,40 +1686,7 @@ }, "paint": { "text-color": "#5A5048", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "overture-building-names", - "type": "symbol", - "source": "overture_buildings", - "source-layer": "overture_building", - "minzoom": 17, - "filter": [ - "has", - "names" - ], - "layout": { - "text-field": [ - "coalesce", - [ - "get", - "display_name" - ], - "" - ], - "text-font": [ - "Noto Sans Regular" - ], - "text-size": 10, - "text-anchor": "center", - "text-allow-overlap": false, - "text-max-width": 8 - }, - "paint": { - "text-color": "#5A5048", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.95)", "text-halo-width": 1.5 } }, @@ -1443,7 +1703,7 @@ "unclassified", "living_street" ], - "minzoom": 16, + "minzoom": 15, "layout": { "text-field": [ "coalesce", @@ -1468,24 +1728,22 @@ [ "zoom" ], - 16, + 15, 10, 18, 13 ], "symbol-placement": "line", - "text-rotation-alignment": "map", - "text-pitch-alignment": "viewport", - "text-keep-upright": true, - "text-max-angle": 25, + "text-letter-spacing": 0.05, + "text-padding": 15, "symbol-spacing": 300, - "text-letter-spacing": 0.04, - "text-padding": 8, - "text-allow-overlap": false + "text-max-angle": 30, + "text-allow-overlap": false, + "text-ignore-placement": false }, "paint": { "text-color": "#4A5568", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.92)", "text-halo-width": 1.8 } }, @@ -1536,74 +1794,19 @@ 16 ], "symbol-placement": "line", - "text-rotation-alignment": "map", - "text-pitch-alignment": "viewport", - "text-keep-upright": true, - "text-max-angle": 22, + "text-letter-spacing": 0.06, + "text-padding": 20, "symbol-spacing": 350, - "text-letter-spacing": 0.05, - "text-padding": 12, - "text-allow-overlap": false + "text-max-angle": 25, + "text-allow-overlap": false, + "text-ignore-placement": false }, "paint": { "text-color": "#1A2332", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.95)", "text-halo-width": 2.5 } }, - { - "id": "overture-street-names", - "type": "symbol", - "source": "overture_segments", - "source-layer": "overture_segment", - "minzoom": 14, - "filter": [ - "has", - "names" - ], - "layout": { - "text-field": [ - "coalesce", - [ - "get", - "display_name" - ], - "" - ], - "text-font": [ - "Noto Sans Regular" - ], - "text-size": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 14, - 9, - 16, - 12, - 18, - 14 - ], - "symbol-placement": "line", - "text-rotation-alignment": "map", - "text-pitch-alignment": "viewport", - "text-keep-upright": true, - "text-max-angle": 28, - "symbol-spacing": 280, - "text-letter-spacing": 0.04, - "text-padding": 15, - "text-allow-overlap": false - }, - "paint": { - "text-color": "#4A5568", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 2 - } - }, { "id": "poi-hospital", "type": "symbol", @@ -1616,8 +1819,8 @@ "hospital" ], "layout": { - "icon-image": "hospital_11", - "icon-size": 1.0, + "icon-image": "hospital", + "icon-size": 1, "text-field": [ "coalesce", [ @@ -1643,22 +1846,106 @@ }, "paint": { "text-color": "#C0392B", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "white", "text-halo-width": 2 } }, { - "id": "poi-doctor-clinic", + "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", - "clinic", - "doctors", - "dentist" + "restaurant", + "cafe", + "fast_food" ], "layout": { "icon-image": [ @@ -1667,9 +1954,9 @@ "get", "amenity" ], - "dentist", - "dentist_11", - "doctor_11" + "cafe", + "cafe", + "restaurant" ], "icon-size": 0.8, "text-field": [ @@ -1692,114 +1979,14 @@ 0, 1.2 ], - "text-anchor": "top", - "text-allow-overlap": false + "text-anchor": "top" }, "paint": { - "text-color": "#C0392B", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-color": "#3D4A5C", + "text-halo-color": "white", "text-halo-width": 1.5 } }, - { - "id": "poi-pharmacy", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 15, - "filter": [ - "==", - "amenity", - "pharmacy" - ], - "layout": { - "icon-image": "pharmacy_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#1A7A3C", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 2 - } - }, - { - "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": [ - "match", - [ - "get", - "religion" - ], - "muslim", - "religious_muslim_11", - "christian", - "religious_christian_11", - "jewish", - "religious_jewish_11", - "place_of_worship_11" - ], - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#1A6B3A", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 2 - } - }, { "id": "poi-school", "type": "symbol", @@ -1814,16 +2001,7 @@ "college" ], "layout": { - "icon-image": [ - "match", - [ - "get", - "amenity" - ], - "school", - "school_11", - "college_11" - ], + "icon-image": "college", "icon-size": 0.8, "text-field": [ "coalesce", @@ -1845,676 +2023,14 @@ 0, 1.2 ], - "text-anchor": "top", - "text-allow-overlap": false + "text-anchor": "top" }, "paint": { "text-color": "#5A4A8A", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "white", "text-halo-width": 2 } }, - { - "id": "poi-restaurant", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 15, - "filter": [ - "in", - "amenity", - "restaurant", - "fast_food" - ], - "layout": { - "icon-image": [ - "match", - [ - "get", - "amenity" - ], - "fast_food", - "fast_food_11", - "restaurant_11" - ], - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#B8530A", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-cafe", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 15, - "filter": [ - "==", - "amenity", - "cafe" - ], - "layout": { - "icon-image": "cafe_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#7B4F2A", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-bakery", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 16, - "filter": [ - "in", - "shop", - "bakery", - "pastry" - ], - "layout": { - "icon-image": "bakery_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#8B6B3D", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-shopping", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 14, - "filter": [ - "any", - [ - "in", - "shop", - "mall", - "department_store", - "supermarket", - "clothes", - "shoes", - "jewelry", - "electronics", - "mobile_phone", - "furniture", - "convenience", - "general", - "variety_store" - ], - [ - "in", - "amenity", - "marketplace" - ] - ], - "layout": { - "icon-image": [ - "match", - [ - "get", - "shop" - ], - "clothes", - "clothing_store_11", - "shoes", - "clothing_store_11", - "supermarket", - "grocery_11", - "convenience", - "grocery_11", - "shop_11" - ], - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#6A3D9A", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-fuel", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 14, - "filter": [ - "==", - "amenity", - "fuel" - ], - "layout": { - "icon-image": "fuel_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#2D6A4F", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-bank", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 15, - "filter": [ - "in", - "amenity", - "bank", - "atm" - ], - "layout": { - "icon-image": "bank_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#2C5282", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-police", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 14, - "filter": [ - "==", - "amenity", - "police" - ], - "layout": { - "icon-image": "police_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#2B6CB0", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-fire-station", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 14, - "filter": [ - "==", - "amenity", - "fire_station" - ], - "layout": { - "icon-image": "fire_station_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#E53E3E", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-hotel", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 14, - "filter": [ - "in", - "tourism", - "hotel", - "motel", - "hostel", - "guest_house" - ], - "layout": { - "icon-image": "lodging_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#744210", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-parking", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 16, - "filter": [ - "==", - "amenity", - "parking" - ], - "layout": { - "icon-image": "car_11", - "icon-size": 0.7, - "text-field": "", - "text-optional": true - }, - "paint": { - "icon-opacity": 0.7 - } - }, - { - "id": "poi-toilets", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 16, - "filter": [ - "==", - "amenity", - "toilets" - ], - "layout": { - "icon-image": "toilet_11", - "icon-size": 0.7, - "text-field": "", - "text-optional": true - }, - "paint": { - "icon-opacity": 0.7 - } - }, - { - "id": "poi-post-office", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 15, - "filter": [ - "==", - "amenity", - "post_office" - ], - "layout": { - "icon-image": "post_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#4A5568", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-library", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 15, - "filter": [ - "==", - "amenity", - "library" - ], - "layout": { - "icon-image": "library_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#4A5568", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-cinema", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 15, - "filter": [ - "in", - "amenity", - "cinema", - "theatre" - ], - "layout": { - "icon-image": [ - "match", - [ - "get", - "amenity" - ], - "theatre", - "theatre_11", - "cinema_11" - ], - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#805AD5", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, - { - "id": "poi-museum", - "type": "symbol", - "source": "local-osm-points", - "source-layer": "planet_osm_point", - "minzoom": 14, - "filter": [ - "in", - "tourism", - "museum", - "gallery" - ], - "layout": { - "icon-image": "museum_11", - "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", - "text-allow-overlap": false - }, - "paint": { - "text-color": "#6B4C3B", - "text-halo-color": "rgba(255,255,255,0.93)", - "text-halo-width": 1.5 - } - }, { "id": "poi-transit-station", "type": "symbol", @@ -2550,7 +2066,7 @@ ] ], "layout": { - "icon-image": "railway_11", + "icon-image": "rail", "icon-size": 1, "text-field": [ "coalesce", @@ -2577,10 +2093,234 @@ }, "paint": { "text-color": "#CC2233", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.95)", "text-halo-width": 2 } }, + { + "id": "poi-shop", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "all", + [ + "has", + "shop" + ], + [ + "!in", + "shop", + "vacant", + "no" + ] + ], + "layout": { + "icon-image": "shop", + "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": "#7B3FA0", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-police", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 15, + "filter": [ + "in", + "amenity", + "police", + "fire_station" + ], + "layout": { + "icon-image": "police", + "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": "#2F5AA8", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-bank", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "in", + "amenity", + "bank", + "bureau_de_change", + "atm" + ], + "layout": { + "icon-image": "shop", + "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": "#2E6B4F", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-fuel", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 14, + "filter": [ + "==", + "amenity", + "fuel" + ], + "layout": { + "icon-image": "shop", + "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": "#B5651D", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-hotel", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "in", + "tourism", + "hotel", + "motel", + "guest_house", + "hostel" + ], + "layout": { + "icon-image": "tourist", + "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": "#8A5A8A", + "text-halo-color": "white", + "text-halo-width": 1.5 + } + }, { "id": "place-labels-area", "type": "symbol", @@ -2621,11 +2361,12 @@ 13 ], "text-padding": 8, - "text-allow-overlap": false + "text-allow-overlap": false, + "text-ignore-placement": false }, "paint": { "text-color": "#34495E", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.85)", "text-halo-width": 2 } }, @@ -2736,13 +2477,15 @@ "#1A2740", "town", "#2C3E50", + "village", + "#3D4F62", "suburb", - "#34495E", + "#4A5568", "neighbourhood", - "#34495E", - "#34495E" + "#556677", + "#607080" ], - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255,255,255,0.92)", "text-halo-width": [ "match", [ @@ -2763,11 +2506,6 @@ "source": "places_egypt", "source-layer": "places_egypt", "minzoom": 12, - "filter": [ - "!in", - "category", - "street" - ], "layout": { "text-field": [ "coalesce", @@ -2807,7 +2545,7 @@ }, "paint": { "text-color": "#2D3748", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255, 255, 255, 0.9)", "text-halo-width": 2 } }, @@ -2861,7 +2599,7 @@ }, "paint": { "text-color": "#2D3748", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255, 255, 255, 0.9)", "text-halo-width": 2 } }, @@ -2915,9 +2653,150 @@ }, "paint": { "text-color": "#2D3748", - "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-color": "rgba(255, 255, 255, 0.9)", "text-halo-width": 2 } + }, + { + "id": "places-iraq-labels", + "type": "symbol", + "source": "places_iraq", + "source-layer": "places_iraq", + "minzoom": 10, + "filter": [ + "!in", + "category", + "street" + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name_ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Bold" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 12, + 9, + 16, + 13 + ], + "text-offset": [ + 0, + 1.5 + ], + "text-anchor": "top", + "text-padding": 8, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#2D3748", + "text-halo-color": "rgba(255, 255, 255, 0.9)", + "text-halo-width": 2 + } + }, + { + "id": "overture-building-names", + "type": "symbol", + "source": "overture_buildings", + "source-layer": "overture_building", + "minzoom": 16, + "filter": [ + "has", + "name_primary" + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name_primary" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-anchor": "center", + "text-allow-overlap": false, + "text-max-width": 8 + }, + "paint": { + "text-color": "#5a5248", + "text-halo-color": "rgba(255, 255, 255, 0.9)", + "text-halo-width": 1.5 + } + }, + { + "id": "overture-street-names", + "type": "symbol", + "source": "overture_segments", + "source-layer": "overture_segment", + "minzoom": 14, + "filter": [ + "has", + "name_primary" + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name_primary" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 14, + 9, + 16, + 12, + 18, + 14 + ], + "symbol-placement": "line", + "text-rotation-alignment": "map", + "text-pitch-alignment": "viewport", + "text-keep-upright": true, + "text-padding": 20, + "text-letter-spacing": 0.04, + "text-max-angle": 30, + "symbol-spacing": 300, + "text-allow-overlap": false, + "text-ignore-placement": false + }, + "paint": { + "text-color": "#4A5568", + "text-halo-color": "rgba(255, 255, 255, 0.95)", + "text-halo-width": 2.2 + } } ] } \ No newline at end of file