From 790bfcefc8a4b7c042a60c1ee827221b7b62916a Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 14 Jul 2026 21:46:48 +0300 Subject: [PATCH] feat: add Binance Pay webhook handler and implement payment order creation in billing service --- .DS_Store | Bin 6148 -> 8196 bytes .env | 5 +- apps/.DS_Store | Bin 6148 -> 6148 bytes apps/api/.DS_Store | Bin 6148 -> 6148 bytes apps/api/src/billing/billing.controller.ts | 72 + .../src/billing/providers/binance.provider.ts | 120 +- .../flutter-sdk/.flutter-plugins-dependencies | 2 +- packages/flutter-sdk/CHANGELOG.md | 32 + packages/flutter-sdk/README.md | 3 +- packages/flutter-sdk/assets/style.json | 2178 +++++++++++++++++ packages/flutter-sdk/assets/style_dark.json | 2178 +++++++++++++++++ .../.dart_tool/extension_discovery/README.md | 31 + .../extension_discovery/vs_code.json | 1 + .../example/.dart_tool/package_config.json | 250 ++ .../example/.dart_tool/package_graph.json | 337 +++ .../example/.flutter-plugins-dependencies | 1 + packages/flutter-sdk/example/lib/main.dart | 78 + packages/flutter-sdk/example/pubspec.lock | 305 +++ packages/flutter-sdk/example/pubspec.yaml | 20 + packages/flutter-sdk/lib/intaleq_maps.dart | 12 +- .../lib/src/intaleq_map_controller.dart | 177 +- .../lib/src/intaleq_map_widget.dart | 133 +- .../flutter-sdk/lib/src/models/bitmap.dart | 4 - .../flutter-sdk/lib/src/models/geometry.dart | 221 +- .../flutter-sdk/lib/src/models/types.dart | 152 +- .../flutter-sdk/lib/src/offline_service.dart | 119 + packages/flutter-sdk/lib/src/styles.dart | 18 +- packages/flutter-sdk/pubspec.lock | 26 +- packages/flutter-sdk/pubspec.yaml | 13 +- 29 files changed, 6366 insertions(+), 122 deletions(-) create mode 100644 packages/flutter-sdk/assets/style.json create mode 100644 packages/flutter-sdk/assets/style_dark.json create mode 100644 packages/flutter-sdk/example/.dart_tool/extension_discovery/README.md create mode 100644 packages/flutter-sdk/example/.dart_tool/extension_discovery/vs_code.json create mode 100644 packages/flutter-sdk/example/.dart_tool/package_config.json create mode 100644 packages/flutter-sdk/example/.dart_tool/package_graph.json create mode 100644 packages/flutter-sdk/example/.flutter-plugins-dependencies create mode 100644 packages/flutter-sdk/example/lib/main.dart create mode 100644 packages/flutter-sdk/example/pubspec.lock create mode 100644 packages/flutter-sdk/example/pubspec.yaml create mode 100644 packages/flutter-sdk/lib/src/offline_service.dart diff --git a/.DS_Store b/.DS_Store index 360f80666cc2f5c3760f864e2254dba7b1d1786c..e3289b814362bb9e60abe5aa10a4a6edcfc8afa0 100644 GIT binary patch literal 8196 zcmeHM&1(}u6n|qjHnAA$$sVe#H|wET`~syCOFVcG#GDFZ%}1MxNj9cQgr4>v@Z!N+ zp(hb3h=+=zg5aS=@Z=X>y@;3MrS(?)%?H`HZnE}Jq|li%^IPV{#Db?j*7r?PwLS3Pcr!as9~q)k|S}Io95o3%(7Un^;09@)oFpzj>bb)c1u&ZsU1Z zsxKBwt0u3Bt$9J-UUr`MV$Bcb?c|&Fs#mXIQa)A=QjwbE(LI`{8C*>RTj1ED8O|_y z{QmxUZ_fE)dhpE3YsEQceSR0)i{ST_T>Pc)tE|`-I9q8zbI8~@x3@t1l8J@2`M-7IdK9pvHqOj(+@Xp{?PmVkC=e-c4PQ` zjC?&QZzYAZSOD`o7%Mmdp>s*eFIRi{aK7tKb?U;J(lfFa-{Ckv&WjHDlGgxKEh%*y z@?7%WP2c_0jCycR@77*Tz0v3IuXQ-1{$89<`q@&LPYb-}I3IjTd>rDVJUE}Jv?*M&nJ>_eS8 zJY#BW<1jLIZ9M0J2g}QkbWUauvi7p`yy^4Tr0%v^1$I(_gkEe|*8i9LfB)afly)tv zfK_0ZDIlC&J~s(crnZjIleM;sehXbT>E~-yAsAFS4pikhu=@`~T)S|UF|`DRiFzy3N#YMTI9Ct*OPr)l+kYN~{oS$0&)WcvE^#Me&FeEY* z0AX=XIzrBP=G0W6+~maqqHK>`9D+9Op1e;$3dX!AAjNDwdBWu10y2}Y3oc|4U-i*s z@&+NL$@7G`*p{_}oHco!kRF1mB!FzO`05J`lM{qRCTj@uGJ|YM5Eh&4C(Ok*OJ)hb z^|r}z!b%9H*yK6Fyi7nvtA!PrBp*&bCoIQ$BjtB2%jA#3ig1?bWE~M+rVS4!pB5>Z vm@2WDor6OVWHb;6a03ZfQ21_4{LVa?U&a#@+6+J=K_L%{42I3|Jad=Y5S=wC5>qIOALW5yr;@_T!c$@=CaAQNf*>L@F#DGI*qM9B?cNfRn%yd8iF%1B1!YWjV`wAnXN^gh zwG052USo__Xc=0e)mXF`gaKjTuQ9;iZa0lnof=dl=liXev-y>v*5HQwUgobl#npmb zu2>C<@Tbm(u3wLz&SyHumcAX^s6_SqM?)-03G6KmT5~(=*_wWcJZ>+yhl^7S=eif0 zR|}%*aj#o%aATkArKyC9o7l~Y6kyh*l3P2Ke&*V3qTD`?CT`w2X&V{!^mU}$U;47e z{Uy;~fZcY}?TXZ6!u7em+)d^W_D(b>AGt0o?fhf&=Es=J+U)~i9i3>)@;yq_KH z`$e~#^!np{38e#ij7Ge|09P)1=u7!U?N7~uWE zgEIOSLxXzjz@V=Hz$DCCFxO`U&|?G8w-_3P1)@wU(4;DR#ZV?4cF)H77DIz3os_PO zb!=s2ZzxJvhuzcRqlsZT>t<8 delta 188 zcmZoMXfc=|#>B)qu~2NHo}wr(0|Nsi1A_pAVQ_MOZUK2~lb-|>;lPwF$Uqn(v~cnv)>TYJ!jtpZ rLKqD;3v!6DOl&CI%+A5j0dyOX|DAa('BINANCE_PAY_API_KEY'); + } + + private get secretKey(): string { + return this.configService.get('BINANCE_PAY_SECRET_KEY'); } /** - * Mock Webhook Signature Verification + * Create the Binance Pay Signature */ - verifySignature(payload: any, signature: string): boolean { - // In production, uses HMAC-SHA512 with Binance Secret Key - return true; + private generateSignature(timestamp: string, nonce: string, body: any): string { + const payload = timestamp + '\n' + nonce + '\n' + JSON.stringify(body) + '\n'; + return crypto + .createHmac('sha512', this.secretKey) + .update(payload) + .digest('hex') + .toUpperCase(); + } + + /** + * Create a Binance Pay Order + */ + async createOrder(tenantId: string, amount: number, plan: string) { + this.logger.log(`Creating Binance Pay order for ${tenantId} - ${plan}`); + + if (!this.apiKey || !this.secretKey) { + this.logger.error('Binance Pay API keys are not configured'); + throw new InternalServerErrorException('Payment provider is not configured properly'); + } + + const nonce = crypto.randomBytes(16).toString('hex'); + const timestamp = Date.now().toString(); + const merchantTradeNo = `txn_${Date.now()}_${Math.floor(Math.random() * 10000)}`; + + const body = { + env: { + terminalType: 'WEB', + }, + merchantTradeNo: merchantTradeNo, + orderAmount: amount, + currency: 'USDT', + goods: { + goodsType: '01', + goodsCategory: 'Z000', + referenceGoodsId: plan, + goodsName: `${plan} Plan Subscription`, + goodsDetail: `Subscription to ${plan} plan for Maps SaaS`, + }, + passThroughInfo: JSON.stringify({ tenantId, plan }), + returnUrl: `https://map-dashboard.intaleqapp.com/dashboard.html#billing`, + }; + + const signature = this.generateSignature(timestamp, nonce, body); + + try { + const response = await axios.post(this.apiUrl, body, { + headers: { + 'Content-Type': 'application/json', + 'BinancePay-Timestamp': timestamp, + 'BinancePay-Nonce': nonce, + 'BinancePay-Certificate-SN': this.apiKey, + 'BinancePay-Signature': signature, + }, + }); + + if (response.data && response.data.status === 'SUCCESS') { + return { + checkoutUrl: response.data.data.checkoutUrl, + orderId: merchantTradeNo, + prepayId: response.data.data.prepayId, + }; + } else { + this.logger.error(`Binance API Error: ${JSON.stringify(response.data)}`); + throw new InternalServerErrorException('Failed to create Binance order'); + } + } catch (error: any) { + this.logger.error(`Error communicating with Binance API: ${error.message}`); + throw new InternalServerErrorException('Failed to communicate with payment provider'); + } + } + + /** + * Verify Webhook Signature + */ + verifySignature(timestamp: string, nonce: string, signature: string, payloadBody: any): boolean { + const payload = timestamp + '\n' + nonce + '\n' + JSON.stringify(payloadBody) + '\n'; + const expectedSignature = crypto + .createHmac('sha512', this.secretKey) + .update(payload) + .digest('hex') + .toUpperCase(); + + try { + return crypto.timingSafeEqual( + Buffer.from(signature || ''), + Buffer.from(expectedSignature) + ); + } catch (e) { + return false; + } } } diff --git a/packages/flutter-sdk/.flutter-plugins-dependencies b/packages/flutter-sdk/.flutter-plugins-dependencies index 2062858..36f1b25 100644 --- a/packages/flutter-sdk/.flutter-plugins-dependencies +++ b/packages/flutter-sdk/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.19.0+2/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.19.0+2/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[],"linux":[],"windows":[],"web":[{"name":"maplibre_gl_web","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl_web-0.19.0+2/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"maplibre_gl","dependencies":["maplibre_gl_web"]},{"name":"maplibre_gl_web","dependencies":[]}],"date_created":"2026-04-17 14:04:31.920711","version":"3.41.2","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.25.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.25.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[],"linux":[],"windows":[],"web":[{"name":"maplibre_gl_web","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl_web-0.25.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"maplibre_gl","dependencies":["maplibre_gl_web"]},{"name":"maplibre_gl_web","dependencies":[]}],"date_created":"2026-07-09 03:25:15.926071","version":"3.41.2","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/packages/flutter-sdk/CHANGELOG.md b/packages/flutter-sdk/CHANGELOG.md index 25e0c07..8e14300 100644 --- a/packages/flutter-sdk/CHANGELOG.md +++ b/packages/flutter-sdk/CHANGELOG.md @@ -1,3 +1,35 @@ +## 2.2.0 + +* Added `onStyleLoaded` callback to `IntaleqMap` to handle style initialization. +* Improved overlay persistence: Markers, Polylines, Circles, and Polygons are now automatically restored after style changes (e.g., toggling Dark Mode). +* Exported MapLibre offline management primitives (`OfflineRegion`, `downloadOfflineRegion`, etc.) for advanced usage. +* Exported `MyLocationRenderMode` and `MyLocationTrackingMode` for location UI customization. +* Fixed `trackCameraPosition` logic to correctly trigger `onCameraIdle`. + +## 2.1.3 + +* Fixed missing `dart:ui` import in `types.dart`. +* Verified compatibility with Flutter 3.22. + +## 2.1.2 + +* Updated dependencies to latest stable versions (`http`, `lints`, `meta`). +* Fixed `onCameraMoveStarted` not being triggered correctly. +* Updated `README.md` to reflect the latest version. + +## 2.1.1 + +* Finalized static analysis and documentation fixes for maximum pub.dev score. +* Renamed deprecated MapLibre components to latest naming conventions. +* Fixed deprecated Color member usage. + +## 2.1.0 + +* Fixed static analysis warnings (unused imports and variables). +* Added comprehensive documentation for core SDK elements. +* Improved API parity with Google Maps Flutter (CameraPosition helpers). +* Added an `example/` project demonstrating SDK integration. + ## 2.0.0 **Breaking redesign — full Google Maps Flutter API parity.** diff --git a/packages/flutter-sdk/README.md b/packages/flutter-sdk/README.md index f7412f5..9b2b9e9 100644 --- a/packages/flutter-sdk/README.md +++ b/packages/flutter-sdk/README.md @@ -20,7 +20,7 @@ A **drop-in replacement for `google_maps_flutter`** backed by MapLibre GL, optim ```yaml dependencies: - intaleq_maps: ^2.0.0 + intaleq_maps: ^2.2.0 ``` --- @@ -160,6 +160,7 @@ IntaleqMap( | `circles` | `Set` | `{}` | Declarative circle set | | `polygons` | `Set` | `{}` | Declarative polygon set | | `onMapCreated` | `MapCreatedCallback?` | — | Fires once map is ready | +| `onStyleLoaded` | `VoidCallback?` | — | Style fully loaded & ready for overlays | | `onTap` | `ArgumentCallback?` | — | Map tap | | `onLongPress` | `ArgumentCallback?` | — | Map long press | | `onCameraMove` | `CameraPositionCallback?` | — | Camera movement | diff --git a/packages/flutter-sdk/assets/style.json b/packages/flutter-sdk/assets/style.json new file mode 100644 index 0000000..972b5f5 --- /dev/null +++ b/packages/flutter-sdk/assets/style.json @@ -0,0 +1,2178 @@ +{ + "version": 8, + "name": "Intaleq Premium — Light v2.9", + "metadata": { + "brand": "Intaleq", + "version": "2.10.0-light", + "description": "Intaleq light theme — buildings at close zoom only, on-road labels, improved arrows" + }, + "center": [ + 36.276008, + 33.513685 + ], + "zoom": 15, + "glyphs": "https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf", + "sprite": "https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite", + "sources": { + "local-osm-polygons": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/planet_osm_polygon/{z}/{x}/{y}" + ], + "maxzoom": 14, + "attribution": "© Intaleq | © OpenStreetMap contributors" + }, + "local-osm-lines": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/planet_osm_line/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "local-osm-points": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/planet_osm_point/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "places_egypt": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/places_egypt/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "places_syria": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/places_syria/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "places_jordan": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/places_jordan/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "overture_buildings": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/overture_building/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "overture_segments": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/overture_segment/{z}/{x}/{y}" + ], + "maxzoom": 14 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "#EEF2F7" + } + }, + { + "id": "landuse-residential", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "residential" + ], + "paint": { + "fill-color": "#F0F4F8", + "fill-opacity": 1.0 + } + }, + { + "id": "landuse-commercial", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "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 + } + }, + { + "id": "landuse-industrial", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "industrial", + "railway" + ], + "paint": { + "fill-color": "#E4E8EE", + "fill-opacity": 1 + } + }, + { + "id": "park-layer", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "in", + "leisure", + "park", + "garden", + "nature_reserve", + "pitch", + "playground" + ], + [ + "in", + "landuse", + "grass", + "meadow", + "forest" + ], + [ + "in", + "natural", + "wood", + "scrub", + "heath" + ] + ], + "paint": { + "fill-color": [ + "match", + [ + "get", + "leisure" + ], + "pitch", + "#9ED4A0", + "playground", + "#B8E6B8", + "#C5E8C5" + ], + "fill-opacity": 0.85 + } + }, + { + "id": "park-outline", + "type": "line", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "leisure", + "park", + "garden", + "nature_reserve" + ], + "paint": { + "line-color": "#9ED4A0", + "line-width": 0.8, + "line-opacity": 0.6 + } + }, + { + "id": "water-polygon", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "in", + "natural", + "water", + "lake", + "bay" + ], + [ + "==", + "waterway", + "riverbank" + ], + [ + "in", + "landuse", + "basin", + "reservoir" + ], + [ + "==", + "amenity", + "fountain" + ] + ], + "paint": { + "fill-color": "#9ECFE8", + "fill-opacity": 0.95 + } + }, + { + "id": "water-polygon-outline", + "type": "line", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "in", + "natural", + "water", + "lake", + "bay" + ], + [ + "==", + "waterway", + "riverbank" + ], + [ + "in", + "landuse", + "basin", + "reservoir" + ] + ], + "paint": { + "line-color": "#6BB8D8", + "line-width": 0.8, + "line-opacity": 0.8 + } + }, + { + "id": "waterway-river", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "waterway", + "river", + "canal" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "!=", + "seasonal", + "yes" + ], + [ + "!=", + "tunnel", + "yes" + ] + ], + "paint": { + "line-color": "#6BB8D8", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1.5, + 14, + 4, + 16, + 7 + ], + "line-opacity": 0.95 + } + }, + { + "id": "waterway-stream-drain", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "waterway", + "stream", + "drain", + "ditch" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "!=", + "seasonal", + "yes" + ], + [ + "!=", + "tunnel", + "yes" + ] + ], + "minzoom": 13, + "paint": { + "line-color": "#7FBFD8", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 13, + 0.8, + 16, + 2.5 + ], + "line-opacity": 0.85 + } + }, + { + "id": "railway-area", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "railway" + ], + "paint": { + "fill-color": "#DDE2EA", + "fill-opacity": 0.9 + } + }, + { + "id": "railway-rail-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "railway", + "rail", + "narrow_gauge", + "preserved" + ], + [ + "!=", + "service", + "yard" + ], + [ + "!=", + "service", + "siding" + ] + ], + "minzoom": 8, + "paint": { + "line-color": "#B0B8C5", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 8, + 2, + 12, + 4, + 16, + 8 + ] + } + }, + { + "id": "railway-rail-core", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "railway", + "rail", + "narrow_gauge", + "preserved" + ], + [ + "!=", + "service", + "yard" + ], + [ + "!=", + "service", + "siding" + ] + ], + "minzoom": 8, + "paint": { + "line-color": "#6B7A8E", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 8, + 1, + 12, + 2.5, + 16, + 5 + ], + "line-dasharray": [ + 6, + 4 + ] + } + }, + { + "id": "railway-subway-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "railway", + "subway", + "light_rail", + "tram", + "monorail" + ], + "minzoom": 10, + "paint": { + "line-color": [ + "match", + [ + "get", + "railway" + ], + "subway", + "#CC2233", + "light_rail", + "#0066CC", + "tram", + "#8833BB", + "monorail", + "#008855", + "#BB3344" + ], + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 3, + 14, + 6, + 16, + 10 + ] + } + }, + { + "id": "railway-subway-core", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "railway", + "subway", + "light_rail", + "tram", + "monorail" + ], + "minzoom": 10, + "paint": { + "line-color": [ + "match", + [ + "get", + "railway" + ], + "subway", + "#FF3347", + "light_rail", + "#2288FF", + "tram", + "#AA44EE", + "monorail", + "#00BB66", + "#FF4455" + ], + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1.5, + 14, + 3.5, + 16, + 6 + ] + } + }, + { + "id": "road-casing-track-path", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "track", + "path", + "footway", + "cycleway", + "steps" + ], + "minzoom": 14, + "paint": { + "line-color": "#D4D8DF", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 14, + 1, + 16, + 4 + ], + "line-dasharray": [ + 4, + 3 + ] + } + }, + { + "id": "road-casing-minor", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "residential", + "service", + "unclassified", + "living_street", + "pedestrian" + ], + "paint": { + "line-color": "#D4D8DF", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 12, + 1.5, + 16, + 10 + ], + "line-opacity": 0.7 + } + }, + { + "id": "road-core-minor", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "residential", + "service", + "unclassified", + "living_street", + "pedestrian" + ], + "paint": { + "line-color": "#FFFFFF", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 12, + 0.8, + 16, + 8 + ] + } + }, + { + "id": "road-casing-tertiary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "tertiary", + "tertiary_link" + ], + "paint": { + "line-color": "#C9CED8", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 11, + 2, + 16, + 14 + ], + "line-opacity": 0.75 + } + }, + { + "id": "road-core-tertiary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "tertiary", + "tertiary_link" + ], + "paint": { + "line-color": "#FFFFFF", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 11, + 1.2, + 16, + 11 + ] + } + }, + { + "id": "road-casing-secondary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "secondary", + "secondary_link" + ], + "paint": { + "line-color": "#C4CFDE", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 11, + 2.5, + 16, + 16 + ], + "line-opacity": 0.8 + } + }, + { + "id": "road-core-secondary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "secondary", + "secondary_link" + ], + "paint": { + "line-color": "#F8FBFF", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 11, + 1.8, + 16, + 13 + ] + } + }, + { + "id": "road-casing-primary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "primary", + "primary_link" + ], + "paint": { + "line-color": "#C8B868", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 3, + 16, + 18 + ], + "line-opacity": 0.7 + } + }, + { + "id": "road-core-primary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "primary", + "primary_link" + ], + "paint": { + "line-color": "#EDD870", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 2, + 16, + 14 + ] + } + }, + { + "id": "road-casing-motorway", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "motorway", + "motorway_link", + "trunk", + "trunk_link" + ], + "paint": { + "line-color": "#C8A84B", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 9, + 4, + 16, + 20 + ], + "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" + ], + "paint": { + "line-color": "#F0C040", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 9, + 2.5, + 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": 120, + "text-field": "→", + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 15, + 16, + 17, + 22, + 19, + 28 + ], + "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 + }, + "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" + } + }, + { + "id": "railway-label", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "railway", + "rail", + "subway", + "light_rail", + "tram" + ], + "minzoom": 13, + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "symbol-placement": "line", + "text-padding": 6, + "text-allow-overlap": false + }, + "paint": { + "text-color": [ + "match", + [ + "get", + "railway" + ], + "subway", + "#CC2233", + "light_rail", + "#0055BB", + "tram", + "#7722AA", + "#4A5568" + ], + "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-width": 2 + } + }, + { + "id": "waterway-label", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "waterway", + "river", + "canal" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "has", + "name" + ] + ], + "minzoom": 12, + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "symbol-placement": "line", + "text-letter-spacing": 0.1 + }, + "paint": { + "text-color": "#2E86AB", + "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-width": 2 + } + }, + { + "id": "building-number-polygon", + "type": "symbol", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 17, + "filter": [ + "has", + "addr:housenumber" + ], + "layout": { + "text-field": "{addr:housenumber}", + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#5A5048", + "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-width": 1.5 + } + }, + { + "id": "building-number-point", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 17, + "filter": [ + "has", + "addr:housenumber" + ], + "layout": { + "text-field": "{addr:housenumber}", + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#5A5048", + "text-halo-color": "rgba(255,255,255,0.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-width": 1.5 + } + }, + { + "id": "road-labels-minor", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "residential", + "service", + "unclassified", + "living_street" + ], + "minzoom": 16, + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 16, + 10, + 18, + 13 + ], + "symbol-placement": "line", + "text-rotation-alignment": "map", + "text-pitch-alignment": "viewport", + "text-keep-upright": true, + "text-max-angle": 25, + "symbol-spacing": 300, + "text-letter-spacing": 0.04, + "text-padding": 8, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#4A5568", + "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-width": 1.8 + } + }, + { + "id": "road-labels-major", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "primary", + "secondary", + "tertiary", + "motorway", + "trunk" + ], + "minzoom": 12, + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Bold" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 12, + 10, + 14, + 13, + 18, + 16 + ], + "symbol-placement": "line", + "text-rotation-alignment": "map", + "text-pitch-alignment": "viewport", + "text-keep-upright": true, + "text-max-angle": 22, + "symbol-spacing": 350, + "text-letter-spacing": 0.05, + "text-padding": 12, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#1A2332", + "text-halo-color": "rgba(255,255,255,0.93)", + "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", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 13, + "filter": [ + "==", + "amenity", + "hospital" + ], + "layout": { + "icon-image": "hospital", + "icon-size": 1.0, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "text-offset": [ + 0, + 1.2 + ], + "text-anchor": "top", + "text-allow-overlap": false + }, + "paint": { + "text-color": "#C0392B", + "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-width": 2 + } + }, + { + "id": "poi-pharmacy", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 15, + "filter": [ + "==", + "amenity", + "pharmacy" + ], + "layout": { + "icon-image": "pharmacy", + "icon-size": 0.8, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 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": "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", + "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", + "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-width": 2 + } + }, + { + "id": "poi-restaurant-cafe", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "in", + "amenity", + "restaurant", + "cafe", + "fast_food" + ], + "layout": { + "icon-image": [ + "match", + [ + "get", + "amenity" + ], + "cafe", + "cafe", + "restaurant" + ], + "icon-size": 0.8, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-offset": [ + 0, + 1.2 + ], + "text-anchor": "top" + }, + "paint": { + "text-color": "#3D4A5C", + "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-transit-station", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 12, + "filter": [ + "any", + [ + "==", + "railway", + "station" + ], + [ + "==", + "railway", + "halt" + ], + [ + "==", + "railway", + "tram_stop" + ], + [ + "==", + "station", + "subway" + ], + [ + "==", + "amenity", + "bus_station" + ] + ], + "layout": { + "icon-image": "rail", + "icon-size": 1, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "text-offset": [ + 0, + 1.4 + ], + "text-anchor": "top", + "text-allow-overlap": false + }, + "paint": { + "text-color": "#CC2233", + "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-width": 2 + } + }, + { + "id": "place-labels-area", + "type": "symbol", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 10, + "filter": [ + "has", + "name" + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 10, + 14, + 13 + ], + "text-padding": 8, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#34495E", + "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-width": 2 + } + }, + { + "id": "place-labels-point", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 10, + "filter": [ + "any", + [ + "in", + "place", + "city", + "town", + "village", + "suburb", + "neighbourhood", + "hamlet", + "locality", + "quarter" + ], + [ + "in", + "natural", + "peak", + "spring" + ] + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + [ + "match", + [ + "get", + "place" + ], + "city", + 16, + "town", + 14, + 11 + ], + 14, + [ + "match", + [ + "get", + "place" + ], + "city", + 20, + "town", + 16, + 13 + ], + 17, + 14 + ], + "text-letter-spacing": [ + "match", + [ + "get", + "place" + ], + "city", + 0.08, + "town", + 0.05, + 0.02 + ], + "text-anchor": "center", + "text-padding": 10, + "text-allow-overlap": false + }, + "paint": { + "text-color": [ + "match", + [ + "get", + "place" + ], + "city", + "#1A2740", + "town", + "#2C3E50", + "suburb", + "#34495E", + "neighbourhood", + "#34495E", + "#34495E" + ], + "text-halo-color": "rgba(255,255,255,0.93)", + "text-halo-width": [ + "match", + [ + "get", + "place" + ], + "city", + 3, + "town", + 2.5, + 2 + ] + } + }, + { + "id": "places-egypt-labels", + "type": "symbol", + "source": "places_egypt", + "source-layer": "places_egypt", + "minzoom": 12, + "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.93)", + "text-halo-width": 2 + } + }, + { + "id": "places-syria-labels", + "type": "symbol", + "source": "places_syria", + "source-layer": "places_syria", + "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.93)", + "text-halo-width": 2 + } + }, + { + "id": "places-jordan-labels", + "type": "symbol", + "source": "places_jordan", + "source-layer": "places_jordan", + "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.93)", + "text-halo-width": 2 + } + } + ] +} diff --git a/packages/flutter-sdk/assets/style_dark.json b/packages/flutter-sdk/assets/style_dark.json new file mode 100644 index 0000000..32ac54a --- /dev/null +++ b/packages/flutter-sdk/assets/style_dark.json @@ -0,0 +1,2178 @@ +{ + "version": 8, + "name": "Intaleq Premium — Dark v2.9", + "metadata": { + "brand": "Intaleq", + "version": "2.10.0-dark", + "description": "Intaleq dark theme — buildings at close zoom only, on-road labels, improved arrows" + }, + "center": [ + 36.276008, + 33.513685 + ], + "zoom": 15, + "glyphs": "https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf", + "sprite": "https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite", + "sources": { + "local-osm-polygons": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/planet_osm_polygon/{z}/{x}/{y}" + ], + "maxzoom": 14, + "attribution": "© Intaleq | © OpenStreetMap contributors" + }, + "local-osm-lines": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/planet_osm_line/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "local-osm-points": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/planet_osm_point/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "places_egypt": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/places_egypt/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "places_syria": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/places_syria/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "places_jordan": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/places_jordan/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "overture_buildings": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/overture_building/{z}/{x}/{y}" + ], + "maxzoom": 14 + }, + "overture_segments": { + "type": "vector", + "tiles": [ + "https://tiles.intaleqapp.com/overture_segment/{z}/{x}/{y}" + ], + "maxzoom": 14 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "#141920" + } + }, + { + "id": "landuse-residential", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "residential" + ], + "paint": { + "fill-color": "#1A2130", + "fill-opacity": 0.9 + } + }, + { + "id": "landuse-commercial", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "commercial" + ], + "paint": { + "fill-color": "#1E1C18", + "fill-opacity": 0.9 + } + }, + { + "id": "landuse-cemetery", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "cemetery" + ], + "paint": { + "fill-color": "#1A2E1A", + "fill-opacity": 0.9 + } + }, + { + "id": "landuse-military", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "military" + ], + "paint": { + "fill-color": "#28241C", + "fill-opacity": 0.9 + } + }, + { + "id": "landuse-industrial", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "landuse", + "industrial", + "railway" + ], + "paint": { + "fill-color": "#1C2028", + "fill-opacity": 1 + } + }, + { + "id": "park-layer", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "in", + "leisure", + "park", + "garden", + "nature_reserve", + "pitch", + "playground" + ], + [ + "in", + "landuse", + "grass", + "meadow", + "forest" + ], + [ + "in", + "natural", + "wood", + "scrub", + "heath" + ] + ], + "paint": { + "fill-color": [ + "match", + [ + "get", + "leisure" + ], + "pitch", + "#1A3020", + "playground", + "#1C2A1C", + "#172217" + ], + "fill-opacity": 0.85 + } + }, + { + "id": "park-outline", + "type": "line", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "in", + "leisure", + "park", + "garden", + "nature_reserve" + ], + "paint": { + "line-color": "#1A3020", + "line-width": 0.8, + "line-opacity": 0.6 + } + }, + { + "id": "water-polygon", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "in", + "natural", + "water", + "lake", + "bay" + ], + [ + "==", + "waterway", + "riverbank" + ], + [ + "in", + "landuse", + "basin", + "reservoir" + ], + [ + "==", + "amenity", + "fountain" + ] + ], + "paint": { + "fill-color": "#0D2A40", + "fill-opacity": 0.95 + } + }, + { + "id": "water-polygon-outline", + "type": "line", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "any", + [ + "in", + "natural", + "water", + "lake", + "bay" + ], + [ + "==", + "waterway", + "riverbank" + ], + [ + "in", + "landuse", + "basin", + "reservoir" + ] + ], + "paint": { + "line-color": "#1A4A6A", + "line-width": 0.8, + "line-opacity": 0.8 + } + }, + { + "id": "waterway-river", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "waterway", + "river", + "canal" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "!=", + "seasonal", + "yes" + ], + [ + "!=", + "tunnel", + "yes" + ] + ], + "paint": { + "line-color": "#1A4A6A", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1.5, + 14, + 4, + 16, + 7 + ], + "line-opacity": 0.95 + } + }, + { + "id": "waterway-stream-drain", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "waterway", + "stream", + "drain", + "ditch" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "!=", + "seasonal", + "yes" + ], + [ + "!=", + "tunnel", + "yes" + ] + ], + "minzoom": 13, + "paint": { + "line-color": "#153A52", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 13, + 0.8, + 16, + 2.5 + ], + "line-opacity": 0.85 + } + }, + { + "id": "railway-area", + "type": "fill", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "filter": [ + "==", + "landuse", + "railway" + ], + "paint": { + "fill-color": "#202830", + "fill-opacity": 0.9 + } + }, + { + "id": "railway-rail-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "railway", + "rail", + "narrow_gauge", + "preserved" + ], + [ + "!=", + "service", + "yard" + ], + [ + "!=", + "service", + "siding" + ] + ], + "minzoom": 8, + "paint": { + "line-color": "#3A4050", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 8, + 2, + 12, + 4, + 16, + 8 + ] + } + }, + { + "id": "railway-rail-core", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "railway", + "rail", + "narrow_gauge", + "preserved" + ], + [ + "!=", + "service", + "yard" + ], + [ + "!=", + "service", + "siding" + ] + ], + "minzoom": 8, + "paint": { + "line-color": "#4A5568", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 8, + 1, + 12, + 2.5, + 16, + 5 + ], + "line-dasharray": [ + 6, + 4 + ] + } + }, + { + "id": "railway-subway-casing", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "railway", + "subway", + "light_rail", + "tram", + "monorail" + ], + "minzoom": 10, + "paint": { + "line-color": [ + "match", + [ + "get", + "railway" + ], + "subway", + "#AA1122", + "light_rail", + "#0055AA", + "tram", + "#7722AA", + "monorail", + "#007744", + "#BB3344" + ], + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 3, + 14, + 6, + 16, + 10 + ] + } + }, + { + "id": "railway-subway-core", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "railway", + "subway", + "light_rail", + "tram", + "monorail" + ], + "minzoom": 10, + "paint": { + "line-color": [ + "match", + [ + "get", + "railway" + ], + "subway", + "#FF4455", + "light_rail", + "#33AAFF", + "tram", + "#CC55FF", + "monorail", + "#00DD88", + "#FF4455" + ], + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1.5, + 14, + 3.5, + 16, + 6 + ] + } + }, + { + "id": "road-casing-track-path", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "track", + "path", + "footway", + "cycleway", + "steps" + ], + "minzoom": 14, + "paint": { + "line-color": "#252C38", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 14, + 1, + 16, + 4 + ], + "line-dasharray": [ + 4, + 3 + ] + } + }, + { + "id": "road-casing-minor", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "residential", + "service", + "unclassified", + "living_street", + "pedestrian" + ], + "paint": { + "line-color": "#252C38", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 12, + 1.5, + 16, + 10 + ], + "line-opacity": 0.7 + } + }, + { + "id": "road-core-minor", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "residential", + "service", + "unclassified", + "living_street", + "pedestrian" + ], + "paint": { + "line-color": "#2E3848", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 12, + 0.8, + 16, + 8 + ] + } + }, + { + "id": "road-casing-tertiary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "tertiary", + "tertiary_link" + ], + "paint": { + "line-color": "#2A3040", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 11, + 2, + 16, + 14 + ], + "line-opacity": 0.75 + } + }, + { + "id": "road-core-tertiary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "tertiary", + "tertiary_link" + ], + "paint": { + "line-color": "#38465A", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 11, + 1.2, + 16, + 11 + ] + } + }, + { + "id": "road-casing-secondary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "secondary", + "secondary_link" + ], + "paint": { + "line-color": "#3A3010", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 11, + 2.5, + 16, + 16 + ], + "line-opacity": 0.8 + } + }, + { + "id": "road-core-secondary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "secondary", + "secondary_link" + ], + "paint": { + "line-color": "#4A3E18", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 11, + 1.8, + 16, + 13 + ] + } + }, + { + "id": "road-casing-primary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "primary", + "primary_link" + ], + "paint": { + "line-color": "#5A4A10", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 3, + 16, + 18 + ], + "line-opacity": 0.7 + } + }, + { + "id": "road-core-primary", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "primary", + "primary_link" + ], + "paint": { + "line-color": "#7A6418", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 2, + 16, + 14 + ] + } + }, + { + "id": "road-casing-motorway", + "type": "line", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "motorway", + "motorway_link", + "trunk", + "trunk_link" + ], + "paint": { + "line-color": "#6A5010", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 9, + 4, + 16, + 20 + ], + "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" + ], + "paint": { + "line-color": "#8A6A18", + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 9, + 2.5, + 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": 120, + "text-field": "→", + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 15, + 16, + 17, + 22, + 19, + 28 + ], + "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 + }, + "paint": { + "text-color": "rgba(160,180,210,0.5)", + "text-halo-color": "rgba(0,0,0,0.3)", + "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": "#252C38", + "fill-opacity": 0.85, + "fill-outline-color": "#1E2530" + } + }, + { + "id": "building-3d", + "type": "fill-extrusion", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 16, + "filter": [ + "has", + "building" + ], + "paint": { + "fill-extrusion-color": "#252C38", + "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", + "#2A3A50", + "retail", + "#2E3A4A", + "industrial", + "#1E2A38", + "religious", + "#24203A", + "education", + "#1E3024", + "medical", + "#3A2020", + "#2A3245" + ], + "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": "#252C38", + "fill-opacity": 0.82, + "fill-outline-color": "#1E2530" + } + }, + { + "id": "railway-label", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "railway", + "rail", + "subway", + "light_rail", + "tram" + ], + "minzoom": 13, + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "symbol-placement": "line", + "text-padding": 6, + "text-allow-overlap": false + }, + "paint": { + "text-color": [ + "match", + [ + "get", + "railway" + ], + "subway", + "#FF5566", + "light_rail", + "#0055BB", + "tram", + "#7722AA", + "#4A5568" + ], + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 2 + } + }, + { + "id": "waterway-label", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "all", + [ + "in", + "waterway", + "river", + "canal" + ], + [ + "!=", + "intermittent", + "yes" + ], + [ + "has", + "name" + ] + ], + "minzoom": 12, + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "symbol-placement": "line", + "text-letter-spacing": 0.1 + }, + "paint": { + "text-color": "#4AA8D8", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 2 + } + }, + { + "id": "building-number-polygon", + "type": "symbol", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 17, + "filter": [ + "has", + "addr:housenumber" + ], + "layout": { + "text-field": "{addr:housenumber}", + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#8898A8", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 1.5 + } + }, + { + "id": "building-number-point", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 17, + "filter": [ + "has", + "addr:housenumber" + ], + "layout": { + "text-field": "{addr:housenumber}", + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#8898A8", + "text-halo-color": "rgba(20,25,32,0.92)", + "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": "#8898A8", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 1.5 + } + }, + { + "id": "road-labels-minor", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "residential", + "service", + "unclassified", + "living_street" + ], + "minzoom": 16, + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 16, + 10, + 18, + 13 + ], + "symbol-placement": "line", + "text-rotation-alignment": "map", + "text-pitch-alignment": "viewport", + "text-keep-upright": true, + "text-max-angle": 25, + "symbol-spacing": 300, + "text-letter-spacing": 0.04, + "text-padding": 8, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#8BA0B8", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 1.8 + } + }, + { + "id": "road-labels-major", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "filter": [ + "in", + "highway", + "primary", + "secondary", + "tertiary", + "motorway", + "trunk" + ], + "minzoom": 12, + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Bold" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 12, + 10, + 14, + 13, + 18, + 16 + ], + "symbol-placement": "line", + "text-rotation-alignment": "map", + "text-pitch-alignment": "viewport", + "text-keep-upright": true, + "text-max-angle": 22, + "symbol-spacing": 350, + "text-letter-spacing": 0.05, + "text-padding": 12, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#C8D8E8", + "text-halo-color": "rgba(20,25,32,0.92)", + "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": "#8BA0B8", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 2 + } + }, + { + "id": "poi-hospital", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 13, + "filter": [ + "==", + "amenity", + "hospital" + ], + "layout": { + "icon-image": "hospital", + "icon-size": 1.0, + "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": "#FF6B6B", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 2 + } + }, + { + "id": "poi-pharmacy", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 15, + "filter": [ + "==", + "amenity", + "pharmacy" + ], + "layout": { + "icon-image": "pharmacy", + "icon-size": 0.8, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "text-offset": [ + 0, + 1.2 + ], + "text-anchor": "top", + "text-allow-overlap": false + }, + "paint": { + "text-color": "#4ADB80", + "text-halo-color": "rgba(20,25,32,0.92)", + "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": "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", + "text-allow-overlap": false + }, + "paint": { + "text-color": "#4ACA78", + "text-halo-color": "rgba(20,25,32,0.92)", + "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": "#A888E8", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 2 + } + }, + { + "id": "poi-restaurant-cafe", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 16, + "filter": [ + "in", + "amenity", + "restaurant", + "cafe", + "fast_food" + ], + "layout": { + "icon-image": [ + "match", + [ + "get", + "amenity" + ], + "cafe", + "cafe", + "restaurant" + ], + "icon-size": 0.8, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 10, + "text-offset": [ + 0, + 1.2 + ], + "text-anchor": "top" + }, + "paint": { + "text-color": "#A0B8D0", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 1.5 + } + }, + { + "id": "poi-transit-station", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 12, + "filter": [ + "any", + [ + "==", + "railway", + "station" + ], + [ + "==", + "railway", + "halt" + ], + [ + "==", + "railway", + "tram_stop" + ], + [ + "==", + "station", + "subway" + ], + [ + "==", + "amenity", + "bus_station" + ] + ], + "layout": { + "icon-image": "rail", + "icon-size": 1, + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": 11, + "text-offset": [ + 0, + 1.4 + ], + "text-anchor": "top", + "text-allow-overlap": false + }, + "paint": { + "text-color": "#FF5566", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 2 + } + }, + { + "id": "place-labels-area", + "type": "symbol", + "source": "local-osm-polygons", + "source-layer": "planet_osm_polygon", + "minzoom": 10, + "filter": [ + "has", + "name" + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 10, + 14, + 13 + ], + "text-padding": 8, + "text-allow-overlap": false + }, + "paint": { + "text-color": "#8AA0B8", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 2 + } + }, + { + "id": "place-labels-point", + "type": "symbol", + "source": "local-osm-points", + "source-layer": "planet_osm_point", + "minzoom": 10, + "filter": [ + "any", + [ + "in", + "place", + "city", + "town", + "village", + "suburb", + "neighbourhood", + "hamlet", + "locality", + "quarter" + ], + [ + "in", + "natural", + "peak", + "spring" + ] + ], + "layout": { + "text-field": [ + "coalesce", + [ + "get", + "name:ar" + ], + [ + "get", + "name" + ], + "" + ], + "text-font": [ + "Noto Sans Regular" + ], + "text-size": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + [ + "match", + [ + "get", + "place" + ], + "city", + 16, + "town", + 14, + 11 + ], + 14, + [ + "match", + [ + "get", + "place" + ], + "city", + 20, + "town", + 16, + 13 + ], + 17, + 14 + ], + "text-letter-spacing": [ + "match", + [ + "get", + "place" + ], + "city", + 0.08, + "town", + 0.05, + 0.02 + ], + "text-anchor": "center", + "text-padding": 10, + "text-allow-overlap": false + }, + "paint": { + "text-color": [ + "match", + [ + "get", + "place" + ], + "city", + "#D0E0F0", + "town", + "#B8CDE0", + "suburb", + "#8AA0B8", + "neighbourhood", + "#8AA0B8", + "#8AA0B8" + ], + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": [ + "match", + [ + "get", + "place" + ], + "city", + 3, + "town", + 2.5, + 2 + ] + } + }, + { + "id": "places-egypt-labels", + "type": "symbol", + "source": "places_egypt", + "source-layer": "places_egypt", + "minzoom": 12, + "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": "#A8C0D8", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 2 + } + }, + { + "id": "places-syria-labels", + "type": "symbol", + "source": "places_syria", + "source-layer": "places_syria", + "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": "#A8C0D8", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 2 + } + }, + { + "id": "places-jordan-labels", + "type": "symbol", + "source": "places_jordan", + "source-layer": "places_jordan", + "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": "#A8C0D8", + "text-halo-color": "rgba(20,25,32,0.92)", + "text-halo-width": 2 + } + } + ] +} diff --git a/packages/flutter-sdk/example/.dart_tool/extension_discovery/README.md b/packages/flutter-sdk/example/.dart_tool/extension_discovery/README.md new file mode 100644 index 0000000..9dc6757 --- /dev/null +++ b/packages/flutter-sdk/example/.dart_tool/extension_discovery/README.md @@ -0,0 +1,31 @@ +Extension Discovery Cache +========================= + +This folder is used by `package:extension_discovery` to cache lists of +packages that contains extensions for other packages. + +DO NOT USE THIS FOLDER +---------------------- + + * Do not read (or rely) the contents of this folder. + * Do write to this folder. + +If you're interested in the lists of extensions stored in this folder use the +API offered by package `extension_discovery` to get this information. + +If this package doesn't work for your use-case, then don't try to read the +contents of this folder. It may change, and will not remain stable. + +Use package `extension_discovery` +--------------------------------- + +If you want to access information from this folder. + +Feel free to delete this folder +------------------------------- + +Files in this folder act as a cache, and the cache is discarded if the files +are older than the modification time of `.dart_tool/package_config.json`. + +Hence, it should never be necessary to clear this cache manually, if you find a +need to do please file a bug. diff --git a/packages/flutter-sdk/example/.dart_tool/extension_discovery/vs_code.json b/packages/flutter-sdk/example/.dart_tool/extension_discovery/vs_code.json new file mode 100644 index 0000000..c5bd4cc --- /dev/null +++ b/packages/flutter-sdk/example/.dart_tool/extension_discovery/vs_code.json @@ -0,0 +1 @@ +{"version":2,"entries":[{"package":"intaleq_maps","rootUri":"../../","packageUri":"lib/"},{"package":"intaleq_maps_example","rootUri":"../","packageUri":"lib/"}]} \ No newline at end of file diff --git a/packages/flutter-sdk/example/.dart_tool/package_config.json b/packages/flutter-sdk/example/.dart_tool/package_config.json new file mode 100644 index 0000000..4b916e1 --- /dev/null +++ b/packages/flutter-sdk/example/.dart_tool/package_config.json @@ -0,0 +1,250 @@ +{ + "configVersion": 2, + "packages": [ + { + "name": "archive", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/archive-4.0.9", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "async", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/async-2.13.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "boolean_selector", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "characters", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/characters-1.4.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "clock", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/clock-1.1.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "collection", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/collection-1.19.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "fake_async", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/fake_async-1.3.3", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "ffi", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/ffi-2.2.0", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "flutter", + "rootUri": "file:///Users/hamzaaleghwairyeen/flutter/packages/flutter", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "flutter_test", + "rootUri": "file:///Users/hamzaaleghwairyeen/flutter/packages/flutter_test", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "flutter_web_plugins", + "rootUri": "file:///Users/hamzaaleghwairyeen/flutter/packages/flutter_web_plugins", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "http", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/http-1.6.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "http_parser", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/http_parser-4.1.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "image", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/image-4.8.0", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "intaleq_maps", + "rootUri": "../../", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "leak_tracker", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/leak_tracker-11.0.2", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "leak_tracker_flutter_testing", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "leak_tracker_testing", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.2", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "maplibre_gl", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.25.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "maplibre_gl_platform_interface", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl_platform_interface-0.25.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "maplibre_gl_web", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl_web-0.25.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "matcher", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/matcher-0.12.18", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "material_color_utilities", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/material_color_utilities-0.13.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "meta", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/meta-1.17.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "path", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/path-1.9.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "petitparser", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/petitparser-7.0.2", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "posix", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/posix-6.5.0", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "sky_engine", + "rootUri": "file:///Users/hamzaaleghwairyeen/flutter/bin/cache/pkg/sky_engine", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "source_span", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/source_span-1.10.2", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "stack_trace", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/stack_trace-1.12.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "stream_channel", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/stream_channel-2.1.4", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "string_scanner", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/string_scanner-1.4.1", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "term_glyph", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/term_glyph-1.2.2", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "test_api", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/test_api-0.7.9", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "typed_data", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/typed_data-1.4.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "vector_math", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/vector_math-2.2.0", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "vm_service", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/vm_service-15.1.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "web", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/web-1.1.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "xml", + "rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/xml-6.6.1", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "intaleq_maps_example", + "rootUri": "../", + "packageUri": "lib/", + "languageVersion": "3.0" + } + ], + "generator": "pub", + "generatorVersion": "3.11.0", + "flutterRoot": "file:///Users/hamzaaleghwairyeen/flutter", + "flutterVersion": "3.41.2", + "pubCache": "file:///Users/hamzaaleghwairyeen/.pub-cache" +} diff --git a/packages/flutter-sdk/example/.dart_tool/package_graph.json b/packages/flutter-sdk/example/.dart_tool/package_graph.json new file mode 100644 index 0000000..8332793 --- /dev/null +++ b/packages/flutter-sdk/example/.dart_tool/package_graph.json @@ -0,0 +1,337 @@ +{ + "roots": [ + "intaleq_maps_example" + ], + "packages": [ + { + "name": "intaleq_maps_example", + "version": "0.0.0", + "dependencies": [ + "flutter", + "intaleq_maps" + ], + "devDependencies": [ + "flutter_test" + ] + }, + { + "name": "flutter_test", + "version": "0.0.0", + "dependencies": [ + "clock", + "collection", + "fake_async", + "flutter", + "leak_tracker_flutter_testing", + "matcher", + "meta", + "path", + "stack_trace", + "stream_channel", + "test_api", + "vector_math" + ] + }, + { + "name": "intaleq_maps", + "version": "2.2.0", + "dependencies": [ + "flutter", + "http", + "maplibre_gl" + ] + }, + { + "name": "flutter", + "version": "0.0.0", + "dependencies": [ + "characters", + "collection", + "material_color_utilities", + "meta", + "sky_engine", + "vector_math" + ] + }, + { + "name": "stream_channel", + "version": "2.1.4", + "dependencies": [ + "async" + ] + }, + { + "name": "meta", + "version": "1.17.0", + "dependencies": [] + }, + { + "name": "collection", + "version": "1.19.1", + "dependencies": [] + }, + { + "name": "leak_tracker_flutter_testing", + "version": "3.0.10", + "dependencies": [ + "flutter", + "leak_tracker", + "leak_tracker_testing", + "matcher", + "meta" + ] + }, + { + "name": "vector_math", + "version": "2.2.0", + "dependencies": [] + }, + { + "name": "stack_trace", + "version": "1.12.1", + "dependencies": [ + "path" + ] + }, + { + "name": "clock", + "version": "1.1.2", + "dependencies": [] + }, + { + "name": "fake_async", + "version": "1.3.3", + "dependencies": [ + "clock", + "collection" + ] + }, + { + "name": "path", + "version": "1.9.1", + "dependencies": [] + }, + { + "name": "matcher", + "version": "0.12.18", + "dependencies": [ + "async", + "meta", + "stack_trace", + "term_glyph", + "test_api" + ] + }, + { + "name": "test_api", + "version": "0.7.9", + "dependencies": [ + "async", + "boolean_selector", + "collection", + "meta", + "source_span", + "stack_trace", + "stream_channel", + "string_scanner", + "term_glyph" + ] + }, + { + "name": "http", + "version": "1.6.0", + "dependencies": [ + "async", + "http_parser", + "meta", + "web" + ] + }, + { + "name": "maplibre_gl", + "version": "0.25.0", + "dependencies": [ + "flutter", + "maplibre_gl_platform_interface", + "maplibre_gl_web" + ] + }, + { + "name": "sky_engine", + "version": "0.0.0", + "dependencies": [] + }, + { + "name": "material_color_utilities", + "version": "0.13.0", + "dependencies": [ + "collection" + ] + }, + { + "name": "characters", + "version": "1.4.1", + "dependencies": [] + }, + { + "name": "async", + "version": "2.13.1", + "dependencies": [ + "collection", + "meta" + ] + }, + { + "name": "leak_tracker_testing", + "version": "3.0.2", + "dependencies": [ + "leak_tracker", + "matcher", + "meta" + ] + }, + { + "name": "leak_tracker", + "version": "11.0.2", + "dependencies": [ + "clock", + "collection", + "meta", + "path", + "vm_service" + ] + }, + { + "name": "term_glyph", + "version": "1.2.2", + "dependencies": [] + }, + { + "name": "string_scanner", + "version": "1.4.1", + "dependencies": [ + "source_span" + ] + }, + { + "name": "source_span", + "version": "1.10.2", + "dependencies": [ + "collection", + "path", + "term_glyph" + ] + }, + { + "name": "boolean_selector", + "version": "2.1.2", + "dependencies": [ + "source_span", + "string_scanner" + ] + }, + { + "name": "web", + "version": "1.1.1", + "dependencies": [] + }, + { + "name": "http_parser", + "version": "4.1.2", + "dependencies": [ + "collection", + "source_span", + "string_scanner", + "typed_data" + ] + }, + { + "name": "maplibre_gl_web", + "version": "0.25.0", + "dependencies": [ + "flutter", + "flutter_web_plugins", + "image", + "maplibre_gl_platform_interface", + "meta", + "web" + ] + }, + { + "name": "maplibre_gl_platform_interface", + "version": "0.25.0", + "dependencies": [ + "flutter", + "meta" + ] + }, + { + "name": "vm_service", + "version": "15.1.0", + "dependencies": [] + }, + { + "name": "typed_data", + "version": "1.4.0", + "dependencies": [ + "collection" + ] + }, + { + "name": "image", + "version": "4.8.0", + "dependencies": [ + "archive", + "meta", + "xml" + ] + }, + { + "name": "flutter_web_plugins", + "version": "0.0.0", + "dependencies": [ + "flutter" + ] + }, + { + "name": "xml", + "version": "6.6.1", + "dependencies": [ + "collection", + "meta", + "petitparser" + ] + }, + { + "name": "archive", + "version": "4.0.9", + "dependencies": [ + "path", + "posix" + ] + }, + { + "name": "petitparser", + "version": "7.0.2", + "dependencies": [ + "collection", + "meta" + ] + }, + { + "name": "posix", + "version": "6.5.0", + "dependencies": [ + "ffi", + "meta", + "path" + ] + }, + { + "name": "ffi", + "version": "2.2.0", + "dependencies": [] + } + ], + "configVersion": 1 +} \ No newline at end of file diff --git a/packages/flutter-sdk/example/.flutter-plugins-dependencies b/packages/flutter-sdk/example/.flutter-plugins-dependencies new file mode 100644 index 0000000..8005bf6 --- /dev/null +++ b/packages/flutter-sdk/example/.flutter-plugins-dependencies @@ -0,0 +1 @@ +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.25.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.25.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[],"linux":[],"windows":[],"web":[{"name":"maplibre_gl_web","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl_web-0.25.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"maplibre_gl","dependencies":["maplibre_gl_web"]},{"name":"maplibre_gl_web","dependencies":[]}],"date_created":"2026-07-09 03:25:15.960410","version":"3.41.2","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/packages/flutter-sdk/example/lib/main.dart b/packages/flutter-sdk/example/lib/main.dart new file mode 100644 index 0000000..42a5e93 --- /dev/null +++ b/packages/flutter-sdk/example/lib/main.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:intaleq_maps/intaleq_maps.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Intaleq Maps Example', + theme: ThemeData( + useMaterial3: true, + colorSchemeSeed: Colors.blue, + ), + home: const MapScreen(), + ); + } +} + +class MapScreen extends StatefulWidget { + const MapScreen({super.key}); + + @override + State createState() => _MapScreenState(); +} + +class _MapScreenState extends State { + IntaleqMapController? _controller; + + // Example marker + final Set _markers = { + const Marker( + markerId: MarkerId('damascus'), + position: LatLng(33.5138, 36.2765), + infoWindow: InfoWindow( + title: 'Damascus', + snippet: 'The oldest continuously inhabited city.', + ), + ), + }; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Intaleq Maps Demo'), + ), + body: IntaleqMap( + apiKey: 'YOUR_API_KEY_HERE', // Replace with a valid key + initialCameraPosition: const CameraPosition( + target: LatLng(33.5138, 36.2765), + zoom: 12, + ), + markers: _markers, + onCameraMoveStarted: () { + debugPrint('Camera movement started'); + }, + onMapCreated: (controller) { + setState(() { + _controller = controller; + }); + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + _controller?.animateCamera( + CameraUpdate.newLatLngZoom(const LatLng(31.9454, 35.9284), 12), + ); + }, + child: const Icon(Icons.location_city), + ), + ); + } +} diff --git a/packages/flutter-sdk/example/pubspec.lock b/packages/flutter-sdk/example/pubspec.lock new file mode 100644 index 0000000..9b791be --- /dev/null +++ b/packages/flutter-sdk/example/pubspec.lock @@ -0,0 +1,305 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + url: "https://pub.dev" + source: hosted + version: "4.8.0" + intaleq_maps: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "2.2.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + maplibre_gl: + dependency: transitive + description: + name: maplibre_gl + sha256: d9773555ae4ebab94bbc3ae2176b077cfda486ec729eefe01e1613f164cb8410 + url: "https://pub.dev" + source: hosted + version: "0.25.0" + maplibre_gl_platform_interface: + dependency: transitive + description: + name: maplibre_gl_platform_interface + sha256: bd7de401dea24dd7e8a6f2fa736ddee7dbbee3e24a9027f0afdd619994702047 + url: "https://pub.dev" + source: hosted + version: "0.25.0" + maplibre_gl_web: + dependency: transitive + description: + name: maplibre_gl_web + sha256: af0e48bf96e8dd99f8b958a1953126971eb8a0527b9735441d4f24df3913f5a2 + url: "https://pub.dev" + source: hosted + version: "0.25.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + url: "https://pub.dev" + source: hosted + version: "0.12.18" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" + source: hosted + version: "6.5.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + url: "https://pub.dev" + source: hosted + version: "0.7.9" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499" + url: "https://pub.dev" + source: hosted + version: "15.1.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" +sdks: + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.22.0" diff --git a/packages/flutter-sdk/example/pubspec.yaml b/packages/flutter-sdk/example/pubspec.yaml new file mode 100644 index 0000000..2b988a6 --- /dev/null +++ b/packages/flutter-sdk/example/pubspec.yaml @@ -0,0 +1,20 @@ +name: intaleq_maps_example +description: Demonstrates how to use the Intaleq Maps SDK. +publish_to: 'none' + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + intaleq_maps: + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true diff --git a/packages/flutter-sdk/lib/intaleq_maps.dart b/packages/flutter-sdk/lib/intaleq_maps.dart index c7de275..b537d03 100644 --- a/packages/flutter-sdk/lib/intaleq_maps.dart +++ b/packages/flutter-sdk/lib/intaleq_maps.dart @@ -6,12 +6,22 @@ library intaleq_maps; // Re-export core MapLibre primitives under familiar names export 'package:maplibre_gl/maplibre_gl.dart' - show LatLng, LatLngBounds, CameraUpdate, CameraPosition; + show + LatLng, + LatLngBounds, + OfflineRegion, + OfflineRegionDefinition, + downloadOfflineRegion, + getListOfRegions, + deleteOfflineRegion, + MyLocationRenderMode, + MyLocationTrackingMode; // Public SDK surface export 'src/intaleq_map_widget.dart'; export 'src/intaleq_map_controller.dart'; export 'src/styles.dart'; +export 'src/offline_service.dart'; export 'src/constants/colors.dart'; export 'src/utils/polyline_utils.dart'; diff --git a/packages/flutter-sdk/lib/src/intaleq_map_controller.dart b/packages/flutter-sdk/lib/src/intaleq_map_controller.dart index 4dd1efc..2aa7393 100644 --- a/packages/flutter-sdk/lib/src/intaleq_map_controller.dart +++ b/packages/flutter-sdk/lib/src/intaleq_map_controller.dart @@ -1,11 +1,13 @@ import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; +import 'dart:ui'; import 'package:flutter/services.dart' show rootBundle; import 'package:http/http.dart' as http; import 'package:maplibre_gl/maplibre_gl.dart' as mgl; import 'models/geometry.dart'; import 'models/bitmap.dart'; +import 'models/types.dart'; /// Controls a live [IntaleqMap] widget. /// @@ -13,12 +15,12 @@ import 'models/bitmap.dart'; /// This API mirrors [GoogleMapController] from `google_maps_flutter`. class IntaleqMapController { IntaleqMapController._({ - required mgl.MaplibreMapController raw, + required mgl.MapLibreMapController raw, required String apiKey, }) : _raw = raw, _apiKey = apiKey; - final mgl.MaplibreMapController _raw; + final mgl.MapLibreMapController _raw; final String _apiKey; // ── Internal object registries ───────────────────────────── @@ -39,10 +41,18 @@ class IntaleqMapController { /// Maps our [PolygonId] → live MapLibre [mgl.Fill]. final Map _fills = {}; + /// A single, imperatively-managed "user location" symbol (the moving puck). + /// Kept OUT of the declarative [Marker] sets so it can be updated on every + /// GPS / animation tick without rebuilding the widget tree — see + /// [setUserMarker]. Reset to null on style reload (native symbol is + /// destroyed) so the next update re-adds it. + mgl.Symbol? _userSymbol; + bool _userSymbolBusy = false; + // ── Factory / init ───────────────────────────────────────── static Future create({ - required mgl.MaplibreMapController raw, + required mgl.MapLibreMapController raw, required String apiKey, }) async { final ctrl = IntaleqMapController._(raw: raw, apiKey: apiKey); @@ -61,17 +71,60 @@ class IntaleqMapController { } } + /// Called by the widget when the map style has finished loading/reloading. + /// We clear internal registries because native objects (Symbols, Lines) + /// are destroyed on style reload. + Future onStyleLoaded() async { + _symbols.clear(); + _symbolToMarker.clear(); + _lines.clear(); + _lineToPolyline.clear(); + _circles.clear(); + _fills.clear(); + _loadedImages.clear(); + // The user-location symbol is destroyed with the old style; forget the + // stale handle so the next [setUserMarker] call re-creates it. + _userSymbol = null; + await _registerDefaultImages(); + } + + /// Google Maps draws every marker unconditionally, but MapLibre's symbol + /// layer defaults to `icon-allow-overlap = false`, so custom markers get + /// collision-culled against the basemap's place labels and silently vanish. + /// + /// To match `GoogleMap` semantics (this SDK is a drop-in replacement) we opt + /// marker *icons* out of collision. The symbol manager is created before + /// `onStyleLoadedCallback` fires, so applying this here — before any markers + /// are (re)added on style load — makes pins reliably visible and keeps them + /// visible across dark/light style reloads. + /// + /// Only the icon is forced to always draw; the optional info-window text is + /// left collision-managed so a marker whose artwork already contains a label + /// (e.g. the A/B route pins) doesn't render a duplicate text glyph on top. + Future applyMarkerVisibilityDefaults() async { + try { + await _raw.setSymbolIconAllowOverlap(true); + await _raw.setSymbolIconIgnorePlacement(true); + } catch (_) { + // Symbol manager not ready yet — safe to ignore; defaults reapply on the + // next style load. + } + } + // ── Camera (same API as GoogleMapController) ────────────── /// Animates the camera to the given [update]. - Future animateCamera(mgl.CameraUpdate update) => - _raw.animateCamera(update); + Future animateCamera(CameraUpdate update) => + _raw.animateCamera(update.toMapLibre()); /// Instantly moves the camera to the given [update]. - Future moveCamera(mgl.CameraUpdate update) => _raw.moveCamera(update); + Future moveCamera(CameraUpdate update) => + _raw.moveCamera(update.toMapLibre()); - /// Returns the current [mgl.CameraPosition] of the map. - mgl.CameraPosition? get cameraPosition => _raw.cameraPosition; + /// Returns the current [CameraPosition] of the map. + CameraPosition? get cameraPosition => _raw.cameraPosition != null + ? CameraPosition.fromMapLibre(_raw.cameraPosition!) + : null; /// Returns the current zoom level. Future getZoomLevel() async => _raw.cameraPosition?.zoom ?? 14.0; @@ -91,17 +144,20 @@ class IntaleqMapController { /// Register a custom image into the map. /// Required before using [InlqBitmap.fromBytes] or custom style images. - Future addImage(String imageId, Uint8List bytes) => - _raw.addImage(imageId, bytes); - - // ── Markers ──────────────────────────────────────────────── + Future addImage(String imageId, Uint8List bytes) async { + await _raw.addImage(imageId, bytes); + _loadedImages.add(imageId); + } /// Adds a single [Marker] to the map and returns its MapLibre handle. Future addMarker(Marker marker) async { - await _loadBitmapIfNeeded(marker.icon); final symbol = await _raw.addSymbol(marker.toSymbolOptions()); _symbols[marker.markerId] = symbol; _symbolToMarker[symbol.id] = marker; + + // Ensure collision defaults apply (symbol layer is created lazily by MapLibre) + await applyMarkerVisibilityDefaults(); + return symbol; } @@ -121,6 +177,44 @@ class IntaleqMapController { await _raw.removeSymbol(symbol); } + // ── User-location puck (imperative, high-frequency) ──────── + + /// Places or moves the single "user location" puck. + /// + /// Unlike the declarative [markers] set, this updates one native symbol + /// in place — the correct pattern for a marker that moves on every GPS or + /// animation frame (a vehicle/heading puck). It avoids rebuilding the + /// Flutter tree and the whole-set diff, which otherwise makes a + /// continuously-moving marker flicker or disappear. + /// + /// Safe to call at up to display refresh rate: overlapping calls are + /// coalesced (a call that arrives while another is still in flight is + /// dropped, and the next tick supplies fresh coordinates). The puck is + /// re-created automatically after a style reload. + Future setUserMarker(Marker marker) async { + if (_userSymbolBusy) return; + _userSymbolBusy = true; + try { + await _loadBitmapIfNeeded(marker.icon); + final symbol = _userSymbol; + if (symbol == null) { + _userSymbol = await _raw.addSymbol(marker.toSymbolOptions()); + } else { + await _raw.updateSymbol(symbol, marker.toSymbolOptions()); + } + } finally { + _userSymbolBusy = false; + } + } + + /// Removes the user-location puck, if present. + Future clearUserMarker() async { + final symbol = _userSymbol; + if (symbol == null) return; + _userSymbol = null; + await _raw.removeSymbol(symbol); + } + // ── Polylines ────────────────────────────────────────────── Future addPolyline(Polyline polyline) async { @@ -295,9 +389,9 @@ class IntaleqMapController { /// Searches for places using the Intaleq Geocoding API. Future> searchPlaces(String query) async { - final uri = Uri.https('map-saas.intaleq.com', '/v1/geocoding/search', { + final uri = Uri.https('map-saas.intaleqapp.com', '/api/geocoding/search', { 'q': query, - 'key': _apiKey, + 'api_key': _apiKey, }); final res = await http.get(uri); if (res.statusCode == 200) return jsonDecode(res.body) as List; @@ -306,10 +400,10 @@ class IntaleqMapController { /// Reverse geocodes a [LatLng] to a place description. Future> reverseGeocode(mgl.LatLng position) async { - final uri = Uri.https('map-saas.intaleq.com', '/v1/geocoding/reverse', { + final uri = Uri.https('map-saas.intaleqapp.com', '/api/geocoding/reverse', { 'lat': position.latitude.toString(), 'lng': position.longitude.toString(), - 'key': _apiKey, + 'api_key': _apiKey, }); final res = await http.get(uri); if (res.statusCode == 200) @@ -320,17 +414,22 @@ class IntaleqMapController { /// Fetches a route between [origin] and [destination]. /// /// [profile] is one of: `driving`, `cycling`, `walking`. + /// [steps] when true, returns turn-by-turn navigation steps. /// Returns the raw Intaleq Routing API response. Future> getDirections( mgl.LatLng origin, mgl.LatLng destination, { String profile = 'driving', + bool steps = true, }) async { - final uri = Uri.https('map-saas.intaleq.com', '/v1/routing/route', { - 'start': '${origin.longitude},${origin.latitude}', - 'end': '${destination.longitude},${destination.latitude}', + final uri = Uri.https('map-saas.intaleqapp.com', '/api/maps/route', { + 'fromLat': origin.latitude.toString(), + 'fromLng': origin.longitude.toString(), + 'toLat': destination.latitude.toString(), + 'toLng': destination.longitude.toString(), 'profile': profile, - 'key': _apiKey, + 'steps': steps.toString(), + 'api_key': _apiKey, }); final res = await http.get(uri); if (res.statusCode == 200) @@ -338,6 +437,42 @@ class IntaleqMapController { throw Exception('Routing request failed: ${res.statusCode}'); } + /// High-level helper to fetch and draw a route on the map. + /// + /// Returns the [PolylineId] of the drawn route, or null if it failed. + Future drawRoute({ + required mgl.LatLng origin, + required mgl.LatLng destination, + String profile = 'driving', + Color color = const Color(0xFF2196F3), + double width = 5.0, + }) async { + try { + final data = await getDirections(origin, destination, profile: profile); + if (data['status'] == 'success' && data['data'] != null) { + final List points = data['data']['points']; + final List path = points + .map((p) => mgl.LatLng(p['lat'] as double, p['lng'] as double)) + .toList(); + + final polylineId = + PolylineId('route_${DateTime.now().millisecondsSinceEpoch}'); + final polyline = Polyline( + polylineId: polylineId, + points: path, + color: color, + width: width, + ); + + await addPolyline(polyline); + return polylineId; + } + } catch (e) { + print('IntaleqMapController.drawRoute error: $e'); + } + return null; + } + // ── Bitmap loader helper ─────────────────────────────────── final Set _loadedImages = {}; diff --git a/packages/flutter-sdk/lib/src/intaleq_map_widget.dart b/packages/flutter-sdk/lib/src/intaleq_map_widget.dart index 682c3d0..7a75c2c 100644 --- a/packages/flutter-sdk/lib/src/intaleq_map_widget.dart +++ b/packages/flutter-sdk/lib/src/intaleq_map_widget.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:maplibre_gl/maplibre_gl.dart' as mgl; import 'intaleq_map_controller.dart'; import 'styles.dart'; +import 'offline_service.dart'; import 'models/geometry.dart'; import 'models/types.dart'; @@ -40,9 +42,11 @@ class IntaleqMap extends StatefulWidget { this.polylines = const {}, this.circles = const {}, this.polygons = const {}, - this.onMapCreated, - this.onTap, + this.onLongPress, + this.onMapCreated, + this.onStyleLoaded, + this.onTap, this.onCameraMove, this.onCameraMoveStarted, this.onCameraIdle, @@ -54,6 +58,7 @@ class IntaleqMap extends StatefulWidget { this.scrollGesturesEnabled = true, this.tiltGesturesEnabled = true, this.zoomGesturesEnabled = true, + this.autoCache = true, this.minMaxZoomPreference = MinMaxZoomPreference.unbounded, this.cameraTargetBounds = CameraTargetBounds.unbounded, }); @@ -64,7 +69,7 @@ class IntaleqMap extends StatefulWidget { final String apiKey; /// Starting camera position (target + zoom). - final mgl.CameraPosition initialCameraPosition; + final CameraPosition initialCameraPosition; // ── Map style ────────────────────────────────────────────── @@ -74,6 +79,9 @@ class IntaleqMap extends StatefulWidget { /// Override with a custom MapLibre style URL. final String? styleUrl; + /// Automatically download tiles around the camera center when idle. + final bool autoCache; + // ── Overlays (declarative — mirrors GoogleMap) ───────────── final Set markers; @@ -86,6 +94,10 @@ class IntaleqMap extends StatefulWidget { /// Called once the map is ready. Use [IntaleqMapController] for all operations. final MapCreatedCallback? onMapCreated; + /// Note: If this callback returns a Future, the widget will await it before + /// adding declarative markers and polylines. + final Function()? onStyleLoaded; + // ── Interaction callbacks ────────────────────────────────── /// Called when the user taps the map (not on a marker). @@ -128,10 +140,48 @@ class IntaleqMap extends StatefulWidget { class _IntaleqMapState extends State { IntaleqMapController? _controller; + bool _isCameraMoving = false; + String? _styleString; + bool _isLoadingStyle = true; + + @override + void initState() { + super.initState(); + _loadStyle(); + } + + Future _loadStyle() async { + try { + final url = _resolvedStyleUrl; + print("🔥 [IntaleqMap] Resolving style: $url"); + + if (url.startsWith('asset://')) { + final assetPath = url.replaceFirst('asset://', ''); + _styleString = await rootBundle.loadString(assetPath); + print("🔥 [IntaleqMap] Style loaded from asset string: ${assetPath.split('/').last}"); + } else { + _styleString = url; + print("🔥 [IntaleqMap] Using remote style URL: $url"); + } + } catch (e) { + print("❌ [IntaleqMap] Failed to load style: $e"); + _styleString = 'about:blank'; + } finally { + if (mounted) { + setState(() => _isLoadingStyle = false); + } + } + } @override void didUpdateWidget(IntaleqMap oldWidget) { super.didUpdateWidget(oldWidget); + + if (oldWidget.mapType != widget.mapType || + oldWidget.styleUrl != widget.styleUrl) { + _loadStyle(); + } + final ctrl = _controller; if (ctrl == null) return; @@ -144,15 +194,16 @@ class _IntaleqMapState extends State { String get _resolvedStyleUrl { if (widget.styleUrl != null) return widget.styleUrl!; + // Default to local assets for performance and offline support return switch (widget.mapType) { - IntaleqMapType.normal => IntaleqStyles.obsidian(widget.apiKey), - IntaleqMapType.light => IntaleqStyles.light(widget.apiKey), + IntaleqMapType.normal => 'asset://packages/intaleq_maps/assets/style_dark.json', + IntaleqMapType.light => 'asset://packages/intaleq_maps/assets/style.json', IntaleqMapType.satellite => IntaleqStyles.satellite(widget.apiKey), IntaleqMapType.none => 'about:blank', }; } - Future _onMapCreated(mgl.MaplibreMapController rawCtrl) async { + Future _onMapCreated(mgl.MapLibreMapController rawCtrl) async { // Wire up tap routing before handing the controller to the caller. rawCtrl.onSymbolTapped.add(_onSymbolTapped); rawCtrl.onLineTapped.add(_onLineTapped); @@ -162,13 +213,6 @@ class _IntaleqMapState extends State { apiKey: widget.apiKey, ); _controller = ctrl; - - // Render the initial overlay sets. - for (final m in widget.markers) await ctrl.addMarker(m); - for (final p in widget.polylines) await ctrl.addPolyline(p); - for (final c in widget.circles) await ctrl.addCircle(c); - for (final g in widget.polygons) await ctrl.addPolygon(g); - widget.onMapCreated?.call(ctrl); } @@ -177,25 +221,64 @@ class _IntaleqMapState extends State { void _onLineTapped(mgl.Line line) => _controller?.onLineTapped(line); + Future _onStyleLoaded() async { + final ctrl = _controller; + if (ctrl == null) return; + + await ctrl.onStyleLoaded(); + final callbackResult = widget.onStyleLoaded?.call(); + if (callbackResult is Future) { + await callbackResult; + } + + // Re-render everything from the current declarative sets. + // This ensures overlays persist across style changes (Dark/Light mode) + // and during certain zoom/camera events that trigger style reloads. + for (final m in widget.markers) await ctrl.addMarker(m); + for (final p in widget.polylines) await ctrl.addPolyline(p); + for (final c in widget.circles) await ctrl.addCircle(c); + for (final g in widget.polygons) await ctrl.addPolygon(g); + + // Apply defaults AFTER markers have initialized the symbol layer + await ctrl.applyMarkerVisibilityDefaults(); + } + @override Widget build(BuildContext context) { + if (_isLoadingStyle) { + return const Center(child: CircularProgressIndicator()); + } + return mgl.MaplibreMap( - styleString: _resolvedStyleUrl, - initialCameraPosition: widget.initialCameraPosition, + styleString: _styleString!, + initialCameraPosition: widget.initialCameraPosition.toMapLibre(), onMapCreated: _onMapCreated, + onStyleLoadedCallback: _onStyleLoaded, onMapClick: widget.onTap != null ? (point, latlng) => widget.onTap!(latlng) : null, onMapLongClick: widget.onLongPress != null ? (point, latlng) => widget.onLongPress!(latlng) : null, - onCameraIdle: widget.onCameraIdle, + onCameraIdle: () { + _isCameraMoving = false; + if (widget.autoCache) { + final pos = _controller?.cameraPosition; + if (pos != null) { + IntaleqOfflineService.instance.downloadRegion( + pos.target, + styleUrl: _resolvedStyleUrl, + ); + } + } + widget.onCameraIdle?.call(); + }, onCameraTrackingChanged: null, myLocationEnabled: widget.myLocationEnabled, myLocationRenderMode: widget.myLocationEnabled - ? mgl.MyLocationRenderMode.NORMAL - : mgl.MyLocationRenderMode.NORMAL, - myLocationTrackingMode: mgl.MyLocationTrackingMode.None, + ? mgl.MyLocationRenderMode.normal + : mgl.MyLocationRenderMode.normal, + myLocationTrackingMode: mgl.MyLocationTrackingMode.none, compassEnabled: widget.compassEnabled, rotateGesturesEnabled: widget.rotateGesturesEnabled, scrollGesturesEnabled: widget.scrollGesturesEnabled, @@ -208,7 +291,17 @@ class _IntaleqMapState extends State { cameraTargetBounds: widget.cameraTargetBounds.bounds != null ? mgl.CameraTargetBounds(widget.cameraTargetBounds.bounds!) : mgl.CameraTargetBounds.unbounded, - trackCameraPosition: widget.onCameraMove != null, + trackCameraPosition: + widget.onCameraMove != null || + widget.onCameraMoveStarted != null || + widget.onCameraIdle != null, + onCameraMove: (pos) { + if (!_isCameraMoving && widget.onCameraMoveStarted != null) { + _isCameraMoving = true; + widget.onCameraMoveStarted!(); + } + widget.onCameraMove?.call(CameraPosition.fromMapLibre(pos)); + }, onCameraTrackingDismissed: null, ); } diff --git a/packages/flutter-sdk/lib/src/models/bitmap.dart b/packages/flutter-sdk/lib/src/models/bitmap.dart index 7f8bcf1..2ab86a5 100644 --- a/packages/flutter-sdk/lib/src/models/bitmap.dart +++ b/packages/flutter-sdk/lib/src/models/bitmap.dart @@ -1,5 +1,4 @@ import 'dart:typed_data' show Uint8List; -import 'dart:ui' show Offset; /// Defines a bitmap image for use as a [Marker] icon. /// @@ -9,7 +8,6 @@ class InlqBitmap { const InlqBitmap._({ required this.mapLibreImageId, this.size, - this.offset, this.bytes, this.assetName, }); @@ -20,8 +18,6 @@ class InlqBitmap { /// Optional size multiplier (MapLibre iconSize). final double? size; - /// Optional pixel offset from the anchor point. - final Offset? offset; /// Raw PNG/JPEG bytes (used with [fromBytes]). final Uint8List? bytes; diff --git a/packages/flutter-sdk/lib/src/models/geometry.dart b/packages/flutter-sdk/lib/src/models/geometry.dart index d7aa116..00d4a01 100644 --- a/packages/flutter-sdk/lib/src/models/geometry.dart +++ b/packages/flutter-sdk/lib/src/models/geometry.dart @@ -1,7 +1,6 @@ import 'dart:ui' show Offset, Color; import 'package:flutter/foundation.dart' show VoidCallback, ValueChanged; import 'package:maplibre_gl/maplibre_gl.dart' as mgl; -import '../constants/colors.dart'; import 'bitmap.dart'; import 'info_window.dart'; @@ -9,46 +8,74 @@ import 'info_window.dart'; // ID types (identical contract to google_maps_flutter) // ───────────────────────────────────────────────────────────── +/// Uniquely identifies a [Marker] on the map. class MarkerId { + /// Creates a [MarkerId] with the given [value]. const MarkerId(this.value); + + /// The unique string identifier. final String value; + @override bool operator ==(Object o) => o is MarkerId && o.value == value; + @override int get hashCode => value.hashCode; + @override String toString() => 'MarkerId($value)'; } +/// Uniquely identifies a [Polyline] on the map. class PolylineId { + /// Creates a [PolylineId] with the given [value]. const PolylineId(this.value); + + /// The unique string identifier. final String value; + @override bool operator ==(Object o) => o is PolylineId && o.value == value; + @override int get hashCode => value.hashCode; + @override String toString() => 'PolylineId($value)'; } +/// Uniquely identifies a [Circle] on the map. class CircleId { + /// Creates a [CircleId] with the given [value]. const CircleId(this.value); + + /// The unique string identifier. final String value; + @override bool operator ==(Object o) => o is CircleId && o.value == value; + @override int get hashCode => value.hashCode; + @override String toString() => 'CircleId($value)'; } +/// Uniquely identifies a [Polygon] on the map. class PolygonId { + /// Creates a [PolygonId] with the given [value]. const PolygonId(this.value); + + /// The unique string identifier. final String value; + @override bool operator ==(Object o) => o is PolygonId && o.value == value; + @override int get hashCode => value.hashCode; + @override String toString() => 'PolygonId($value)'; } @@ -57,7 +84,9 @@ class PolygonId { // Marker (mirrors google_maps_flutter Marker) // ───────────────────────────────────────────────────────────── +/// A marker that is placed at a specific geographical location on the map. class Marker { + /// Creates a [Marker]. const Marker({ required this.markerId, required this.position, @@ -76,7 +105,10 @@ class Marker { this.onDragEnd, }); + /// Unique identifier for this marker. final MarkerId markerId; + + /// Geographical location of the marker. final mgl.LatLng position; /// Opacity of the marker icon, from 0.0 (transparent) to 1.0 (opaque). @@ -165,7 +197,24 @@ class Marker { draggable: draggable, zIndex: zIndex.toInt(), textField: infoWindow.title, - iconOffset: icon.offset, + textAnchor: 'bottom', + textOffset: const Offset(0, -3.0), + textSize: 12.0, + textColor: (infoWindow.snippet != null && + (infoWindow.snippet == 'start' || + infoWindow.snippet == 'end' || + infoWindow.snippet!.startsWith('stop_'))) ? '#FFFFFF' : '#000000', + textHaloColor: infoWindow.snippet == 'start' ? '#4CAF50' + : (infoWindow.snippet == 'end' ? '#F44336' + : (infoWindow.snippet == 'stop_0' ? '#FF9800' // Orange + : (infoWindow.snippet == 'stop_1' ? '#9C27B0' // Purple + : '#FFFFFF'))), + textHaloWidth: 3.0, + // The bundled Intaleq styles serve glyphs from a host that only carries + // Noto Sans. Without an explicit font, the annotation layer falls back to + // "Open Sans Regular, Arial Unicode MS Regular"; that glyph request 404s + // and MapLibre then drops the entire symbol — icon included — on iOS. + fontNames: infoWindow.title != null ? const ['Noto Sans Regular'] : null, ); } @@ -179,27 +228,54 @@ class Marker { } @override - bool operator ==(Object o) => o is Marker && o.markerId == markerId; + bool operator ==(Object o) => + o is Marker && + o.markerId == markerId && + _latLngEquals(o.position, position) && + o.alpha == alpha && + o.anchor == anchor && + o.draggable == draggable && + o.flat == flat && + o.icon == icon && + o.infoWindow == infoWindow && + o.rotation == rotation && + o.visible == visible && + o.zIndex == zIndex; @override - int get hashCode => markerId.hashCode; + int get hashCode => Object.hash( + markerId, + _latLngHash(position), + alpha, + anchor, + draggable, + flat, + icon, + infoWindow, + rotation, + visible, + zIndex, + ); } // ───────────────────────────────────────────────────────────── // Polyline (mirrors google_maps_flutter Polyline) // ───────────────────────────────────────────────────────────── +/// A polyline is a list of segments that join a sequence of [mgl.LatLng] locations. class Polyline { + /// Creates a [Polyline]. const Polyline({ required this.polylineId, required this.points, this.color = const Color(0xFF0D47A1), - this.width = 5, + this.width = 5.0, this.visible = true, this.zIndex = 0, this.geodesic = false, this.onTap, }); + /// Unique identifier for this polyline. final PolylineId polylineId; /// The ordered list of points that make up the polyline. @@ -209,7 +285,7 @@ class Polyline { final Color color; /// Line stroke width in screen pixels. - final int width; + final double width; /// Whether the polyline is visible on the map. final bool visible; @@ -225,7 +301,7 @@ class Polyline { Polyline copyWith({ List? points, Color? color, - int? width, + double? width, bool? visible, int? zIndex, bool? geodesic, @@ -243,40 +319,59 @@ class Polyline { ); } - /// Internal: converts to MapLibre LineOptions. +/// Internal: converts to MapLibre LineOptions. mgl.LineOptions toLineOptions() { return mgl.LineOptions( geometry: points, lineColor: _colorToHex(color), - lineWidth: width.toDouble(), - lineOpacity: visible ? color.opacity : 0.0, + lineWidth: width < 5.0 ? 5.0 : width, lineJoin: 'round', + lineOpacity: 0.85, ); } @override - bool operator ==(Object o) => o is Polyline && o.polylineId == polylineId; + bool operator ==(Object o) => + o is Polyline && + o.polylineId == polylineId && + o.color == color && + o.width == width && + o.visible == visible && + o.zIndex == zIndex && + o.geodesic == geodesic && + _latLngListEquals(o.points, points); @override - int get hashCode => polylineId.hashCode; + int get hashCode => Object.hash( + polylineId, + color, + width, + visible, + zIndex, + geodesic, + Object.hashAll(points.map(_latLngHash)), + ); } // ───────────────────────────────────────────────────────────── // Circle (mirrors google_maps_flutter Circle) // ───────────────────────────────────────────────────────────── +/// A circle on the map surface. class Circle { + /// Creates a [Circle]. const Circle({ required this.circleId, required this.center, required this.radius, this.fillColor = const Color(0x1A0D47A1), this.strokeColor = const Color(0xFF0D47A1), - this.strokeWidth = 2, + this.strokeWidth = 2.0, this.visible = true, this.zIndex = 0, this.onTap, }); + /// Unique identifier for this circle. final CircleId circleId; /// Center of the circle. @@ -292,7 +387,7 @@ class Circle { final Color strokeColor; /// Stroke width in pixels. - final int strokeWidth; + final double strokeWidth; final bool visible; final int zIndex; @@ -305,7 +400,7 @@ class Circle { double? radius, Color? fillColor, Color? strokeColor, - int? strokeWidth, + double? strokeWidth, bool? visible, int? zIndex, VoidCallback? onTap, @@ -332,37 +427,58 @@ class Circle { circleRadius: radius / 10, // approximate; use addCircleAccurate for precision circleColor: _colorToHex(fillColor), - circleOpacity: visible ? fillColor.opacity : 0.0, + circleOpacity: visible ? fillColor.a : 0.0, circleStrokeColor: _colorToHex(strokeColor), - circleStrokeWidth: strokeWidth.toDouble(), - circleStrokeOpacity: visible ? strokeColor.opacity : 0.0, + circleStrokeWidth: strokeWidth, + circleStrokeOpacity: visible ? strokeColor.a : 0.0, ); } @override - bool operator ==(Object o) => o is Circle && o.circleId == circleId; + bool operator ==(Object o) => + o is Circle && + o.circleId == circleId && + _latLngEquals(o.center, center) && + o.radius == radius && + o.fillColor == fillColor && + o.strokeColor == strokeColor && + o.strokeWidth == strokeWidth && + o.visible == visible && + o.zIndex == zIndex; @override - int get hashCode => circleId.hashCode; + int get hashCode => Object.hash( + circleId, + _latLngHash(center), + radius, + fillColor, + strokeColor, + strokeWidth, + visible, + zIndex, + ); } // ───────────────────────────────────────────────────────────── // Polygon (mirrors google_maps_flutter Polygon) // ───────────────────────────────────────────────────────────── +/// A polygon on the map surface. class Polygon { + /// Creates a [Polygon]. const Polygon({ required this.polygonId, required this.points, this.holes = const [], this.fillColor = const Color(0x1ABDBDBD), this.strokeColor = const Color(0xFF0D47A1), - this.strokeWidth = 2, + this.strokeWidth = 2.0, this.visible = true, this.zIndex = 0, this.geodesic = false, this.onTap, }); + /// Unique identifier for this polygon. final PolygonId polygonId; /// Outer boundary of the polygon. @@ -378,7 +494,7 @@ class Polygon { final Color strokeColor; /// Outline stroke width in pixels. - final int strokeWidth; + final double strokeWidth; final bool visible; final int zIndex; @@ -392,7 +508,7 @@ class Polygon { List>? holes, Color? fillColor, Color? strokeColor, - int? strokeWidth, + double? strokeWidth, bool? visible, int? zIndex, bool? geodesic, @@ -419,15 +535,34 @@ class Polygon { return mgl.FillOptions( geometry: rings, fillColor: _colorToHex(fillColor), - fillOpacity: visible ? fillColor.opacity : 0.0, + fillOpacity: visible ? fillColor.a : 0.0, fillOutlineColor: _colorToHex(strokeColor), ); } @override - bool operator ==(Object o) => o is Polygon && o.polygonId == polygonId; + bool operator ==(Object o) => + o is Polygon && + o.polygonId == polygonId && + o.fillColor == fillColor && + o.strokeColor == strokeColor && + o.strokeWidth == strokeWidth && + o.visible == visible && + o.zIndex == zIndex && + o.geodesic == geodesic && + _latLngListEquals(o.points, points) && + _latLngListListEquals(o.holes, holes); @override - int get hashCode => polygonId.hashCode; + int get hashCode => Object.hash( + polygonId, + fillColor, + strokeColor, + strokeWidth, + visible, + zIndex, + geodesic, + Object.hashAll(points.map(_latLngHash)), + ); } // ───────────────────────────────────────────────────────────── @@ -440,3 +575,37 @@ String _colorToHex(Color color) { '${color.green.toRadixString(16).padLeft(2, '0')}' '${color.blue.toRadixString(16).padLeft(2, '0')}'; } + +// ───────────────────────────────────────────────────────────── +// LatLng value equality +// +// mgl.LatLng does not override == / hashCode, so two coordinates +// with identical lat/lng are NOT equal by default (identity only). +// Geometry classes (Marker/Polyline/Circle/Polygon) need real +// content equality so that diffMarkers/diffPolylines/etc. can tell +// apart "same shape, unchanged" from "same id, moved/redrawn" — +// otherwise in-place updates are silently skipped. +// ───────────────────────────────────────────────────────────── + +bool _latLngEquals(mgl.LatLng a, mgl.LatLng b) => + identical(a, b) || (a.latitude == b.latitude && a.longitude == b.longitude); + +int _latLngHash(mgl.LatLng p) => Object.hash(p.latitude, p.longitude); + +bool _latLngListEquals(List a, List b) { + if (identical(a, b)) return true; + if (a.length != b.length) return false; + for (var i = 0; i < a.length; i++) { + if (!_latLngEquals(a[i], b[i])) return false; + } + return true; +} + +bool _latLngListListEquals(List> a, List> b) { + if (identical(a, b)) return true; + if (a.length != b.length) return false; + for (var i = 0; i < a.length; i++) { + if (!_latLngListEquals(a[i], b[i])) return false; + } + return true; +} diff --git a/packages/flutter-sdk/lib/src/models/types.dart b/packages/flutter-sdk/lib/src/models/types.dart index 5e23be3..3a8fa79 100644 --- a/packages/flutter-sdk/lib/src/models/types.dart +++ b/packages/flutter-sdk/lib/src/models/types.dart @@ -1,5 +1,5 @@ -import 'package:maplibre_gl/maplibre_gl.dart' - show LatLngBounds, LatLng, CameraPosition; +import 'dart:ui'; +import 'package:maplibre_gl/maplibre_gl.dart' as mgl; import '../intaleq_map_controller.dart'; // ───────────────────────────────────────────────────────────── @@ -48,7 +48,7 @@ class MinMaxZoomPreference { class CameraTargetBounds { const CameraTargetBounds(this.bounds); - final LatLngBounds? bounds; + final mgl.LatLngBounds? bounds; /// Unbounded (default). static const CameraTargetBounds unbounded = CameraTargetBounds(null); @@ -61,6 +61,152 @@ class CameraTargetBounds { // Typedefs (same names as google_maps_flutter) // ───────────────────────────────────────────────────────────── +/// Callback when the map is created. typedef MapCreatedCallback = void Function(IntaleqMapController controller); + +/// Generic callback for an argument of type [T]. typedef ArgumentCallback = void Function(T argument); + +/// Callback for camera position changes. typedef CameraPositionCallback = void Function(CameraPosition position); + +/// Defines a particular camera position. +/// +/// This class mirrors `CameraPosition` from `google_maps_flutter`. +class CameraPosition { + /// Creates an immutable representation of the [CameraPosition]. + const CameraPosition({ + required this.target, + this.bearing = 0.0, + this.tilt = 0.0, + this.zoom = 0.0, + }); + + /// The location that the camera is pointing at. + final mgl.LatLng target; + + /// The direction that the camera is facing in degrees clockwise from north. + final double bearing; + + /// The angle, in degrees, of the camera angle from the perpendicular to the map's surface. + final double tilt; + + /// The zoom level of the camera. + final double zoom; + + /// Serializes this [CameraPosition] to a JSON-compatible map. + Map toMap() { + return { + 'target': [target.latitude, target.longitude], + 'bearing': bearing, + 'tilt': tilt, + 'zoom': zoom, + }; + } + + /// Deserializes a [CameraPosition] from a map. + static CameraPosition fromMap(Map json) { + final targetList = json['target'] as List; + return CameraPosition( + target: mgl.LatLng(targetList[0] as double, targetList[1] as double), + bearing: (json['bearing'] as num).toDouble(), + tilt: (json['tilt'] as num).toDouble(), + zoom: (json['zoom'] as num).toDouble(), + ); + } + + /// Internal: Converts to MapLibre's [mgl.CameraPosition]. + mgl.CameraPosition toMapLibre() { + return mgl.CameraPosition( + target: target, + bearing: bearing, + tilt: tilt, + zoom: zoom, + ); + } + + /// Internal: Creates from MapLibre's [mgl.CameraPosition]. + static CameraPosition fromMapLibre(mgl.CameraPosition pos) { + return CameraPosition( + target: pos.target, + bearing: pos.bearing, + tilt: pos.tilt, + zoom: pos.zoom, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! CameraPosition) return false; + return target == other.target && + bearing == other.bearing && + tilt == other.tilt && + zoom == other.zoom; + } + + @override + int get hashCode => Object.hash(target, bearing, tilt, zoom); + + @override + String toString() => + 'CameraPosition(target: $target, zoom: $zoom, bearing: $bearing, tilt: $tilt)'; +} + +/// Defines a camera move. +/// +/// This class mirrors `CameraUpdate` from `google_maps_flutter` but +/// works with our custom [CameraPosition]. +class CameraUpdate { + CameraUpdate._(this._raw); + final mgl.CameraUpdate _raw; + + /// Returns a [CameraUpdate] that moves the camera to the specified [position]. + static CameraUpdate newCameraPosition(CameraPosition position) => + CameraUpdate._(mgl.CameraUpdate.newCameraPosition(position.toMapLibre())); + + /// Returns a [CameraUpdate] that moves the camera to the specified [latLng]. + static CameraUpdate newLatLng(mgl.LatLng latLng) => + CameraUpdate._(mgl.CameraUpdate.newLatLng(latLng)); + + /// Returns a [CameraUpdate] that moves the camera to the specified [bounds]. + static CameraUpdate newLatLngBounds( + mgl.LatLngBounds bounds, { + double left = 0, + double top = 0, + double right = 0, + double bottom = 0, + }) => + CameraUpdate._(mgl.CameraUpdate.newLatLngBounds( + bounds, + left: left, + top: top, + right: right, + bottom: bottom, + )); + + /// Returns a [CameraUpdate] that moves the camera to the specified [latLng] and [zoom]. + static CameraUpdate newLatLngZoom(mgl.LatLng latLng, double zoom) => + CameraUpdate._(mgl.CameraUpdate.newLatLngZoom(latLng, zoom)); + + /// Returns a [CameraUpdate] that scrolls the camera by the specified pixels. + static CameraUpdate scrollBy(double dx, double dy) => + CameraUpdate._(mgl.CameraUpdate.scrollBy(dx, dy)); + + /// Returns a [CameraUpdate] that zooms the camera by the specified amount. + static CameraUpdate zoomBy(double amount, [Offset? focus]) => + CameraUpdate._(mgl.CameraUpdate.zoomBy(amount, focus)); + + /// Returns a [CameraUpdate] that zooms in the camera. + static CameraUpdate zoomIn() => CameraUpdate._(mgl.CameraUpdate.zoomIn()); + + /// Returns a [CameraUpdate] that zooms out the camera. + static CameraUpdate zoomOut() => CameraUpdate._(mgl.CameraUpdate.zoomOut()); + + /// Returns a [CameraUpdate] that zooms the camera to the specified [zoom] level. + static CameraUpdate zoomTo(double zoom) => + CameraUpdate._(mgl.CameraUpdate.zoomTo(zoom)); + + /// Internal: Converts to MapLibre's [mgl.CameraUpdate]. + mgl.CameraUpdate toMapLibre() => _raw; +} diff --git a/packages/flutter-sdk/lib/src/offline_service.dart b/packages/flutter-sdk/lib/src/offline_service.dart new file mode 100644 index 0000000..6c6cdd5 --- /dev/null +++ b/packages/flutter-sdk/lib/src/offline_service.dart @@ -0,0 +1,119 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:maplibre_gl/maplibre_gl.dart'; +import 'dart:math' as math; + +/// Service for managing offline map regions in Intaleq Maps. +/// +/// This service allows you to download map tiles for a specific region +/// so they can be accessed without an internet connection. +class IntaleqOfflineService { + static final IntaleqOfflineService instance = IntaleqOfflineService._(); + IntaleqOfflineService._(); + + bool _isDownloading = false; + LatLng? _lastDownloadedCenter; + Timer? _debounceTimer; + + /// Calculate bounding box for a given center and radius in km. + LatLngBounds _calculateBounds(LatLng center, double radiusKm) { + const double earthRadius = 6371.0; + + // Latitude degrees per km + double latDelta = (radiusKm / earthRadius) * (180 / math.pi); + // Longitude degrees per km at given latitude + double lngDelta = (radiusKm / earthRadius) * + (180 / math.pi) / + math.cos(center.latitude * math.pi / 180); + + return LatLngBounds( + southwest: + LatLng(center.latitude - latDelta, center.longitude - lngDelta), + northeast: + LatLng(center.latitude + latDelta, center.longitude + lngDelta), + ); + } + + /// Downloads map tiles for a specified radius around a coordinate. + /// + /// [center] is the midpoint of the region. + /// [styleUrl] is the style to download (e.g. from IntaleqStyles). + /// [radiusKm] defines the extent of the download. + void downloadRegion( + LatLng center, { + required String styleUrl, + double radiusKm = 5.0, + double minZoom = 6.0, + double maxZoom = 16.0, + }) { + // Debounce: Wait for user to stop moving for 2 seconds before starting download + _debounceTimer?.cancel(); + _debounceTimer = Timer(const Duration(seconds: 2), () async { + if (_isDownloading) return; + + // Avoid re-downloading if the user hasn't moved significantly (e.g. > 3km) + if (_lastDownloadedCenter != null) { + double distance = _calculateDistance(center, _lastDownloadedCenter!); + if (distance < 3.0) return; + } + + _isDownloading = true; + + try { + final bounds = _calculateBounds(center, radiusKm); + + // iOS native crash guard for relative assets - only download remote styles + // because local assets are already on disk. + if (styleUrl.startsWith('asset://') || (Platform.isIOS && !styleUrl.startsWith('http'))) { + return; + } + + final regionDefinition = OfflineRegionDefinition( + bounds: bounds, + mapStyleUrl: styleUrl, + minZoom: minZoom, + maxZoom: maxZoom, + ); + + _lastDownloadedCenter = center; + + // Use maplibre_gl top-level function + await downloadOfflineRegion( + regionDefinition, + metadata: { + 'name': 'Intaleq-${center.latitude}-${center.longitude}', + 'downloadDate': DateTime.now().toIso8601String(), + }, + ); + } catch (e) { + // Silently fail or log in debug + print("❌ [OfflineService] Download failed: $e"); + } finally { + _isDownloading = false; + } + }); + } + + /// Helper to calculate distance in km using Haversine formula. + double _calculateDistance(LatLng p1, LatLng p2) { + var p = 0.017453292519943295; + var c = math.cos; + var a = 0.5 - + c((p2.latitude - p1.latitude) * p) / 2 + + c(p1.latitude * p) * + c(p2.latitude * p) * + (1 - c((p2.longitude - p1.longitude) * p)) / + 2; + return 12742 * math.asin(math.sqrt(a)); + } + + /// Clears all offline map regions and tiles. + Future clearCache() async { + try { + final List regions = await getListOfRegions(); + for (var region in regions) { + await deleteOfflineRegion(region.id); + } + } catch (_) {} + } +} diff --git a/packages/flutter-sdk/lib/src/styles.dart b/packages/flutter-sdk/lib/src/styles.dart index 0c3458f..38c7713 100644 --- a/packages/flutter-sdk/lib/src/styles.dart +++ b/packages/flutter-sdk/lib/src/styles.dart @@ -3,14 +3,26 @@ class IntaleqStyles { IntaleqStyles._(); /// Dark premium Obsidian style — the Intaleq default. + /// + /// This style is optimized for readability and a premium look. static String obsidian(String apiKey) => - 'https://maps.intaleq.com/styles/obsidian/style.json?key=$apiKey'; + 'https://map-saas.intaleqapp.com/api/maps/style.json?theme=obsidian&api_key=$apiKey'; /// High-contrast light style. + /// + /// Best for daylight use and printing. static String light(String apiKey) => - 'https://maps.intaleq.com/styles/light/style.json?key=$apiKey'; + 'https://map-saas.intaleqapp.com/api/maps/style.json?theme=light&api_key=$apiKey'; /// Satellite imagery with road labels. + /// + /// High-resolution satellite tiles overlaid with Intaleq vector labels. static String satellite(String apiKey) => - 'https://maps.intaleq.com/styles/satellite/style.json?key=$apiKey'; + 'https://map-saas.intaleqapp.com/api/maps/style.json?theme=satellite&api_key=$apiKey'; + + /// Path to the local light style asset. + static const String localLight = 'assets/style.json'; + + /// Path to the local dark style asset. + static const String localDark = 'assets/style_dark.json'; } diff --git a/packages/flutter-sdk/pubspec.lock b/packages/flutter-sdk/pubspec.lock index 6b34157..780c97a 100644 --- a/packages/flutter-sdk/pubspec.lock +++ b/packages/flutter-sdk/pubspec.lock @@ -104,14 +104,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.0" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" leak_tracker: dependency: transitive description: @@ -140,34 +132,34 @@ packages: dependency: "direct dev" description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "5.1.1" maplibre_gl: dependency: "direct main" description: name: maplibre_gl - sha256: "9dd9eebee52f42a45aaa9cdb912afa47845c37007b26a799aa482ecd368804c8" + sha256: d9773555ae4ebab94bbc3ae2176b077cfda486ec729eefe01e1613f164cb8410 url: "https://pub.dev" source: hosted - version: "0.19.0+2" + version: "0.25.0" maplibre_gl_platform_interface: dependency: transitive description: name: maplibre_gl_platform_interface - sha256: a95fa38a3532253f32dfe181389adfe9f402773e58ac902d9c4efad3209e0903 + sha256: bd7de401dea24dd7e8a6f2fa736ddee7dbbee3e24a9027f0afdd619994702047 url: "https://pub.dev" source: hosted - version: "0.19.0+2" + version: "0.25.0" maplibre_gl_web: dependency: transitive description: name: maplibre_gl_web - sha256: "7f1540b384f16f3c9bc8b4ebdfca96fb07f6dab5d9ef4dd0e102985dba238691" + sha256: af0e48bf96e8dd99f8b958a1953126971eb8a0527b9735441d4f24df3913f5a2 url: "https://pub.dev" source: hosted - version: "0.19.0+2" + version: "0.25.0" matcher: dependency: transitive description: @@ -311,4 +303,4 @@ packages: version: "6.6.1" sdks: dart: ">=3.9.0-0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.22.0" diff --git a/packages/flutter-sdk/pubspec.yaml b/packages/flutter-sdk/pubspec.yaml index 15e8425..f57bb22 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.0.0 +version: 2.2.0 homepage: https://intaleqapp.com repository: https://github.com/Hamza-Ayed/intaleq_maps issue_tracker: https://github.com/Hamza-Ayed/intaleq_maps/issues @@ -14,13 +14,16 @@ environment: dependencies: flutter: sdk: flutter - maplibre_gl: ^0.19.0 - http: ^1.1.0 + maplibre_gl: ^0.25.0 + http: ^1.2.0 dev_dependencies: flutter_test: sdk: flutter - lints: ^2.1.0 + lints: ^5.0.0 flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true + assets: + - assets/style.json + - assets/style_dark.json