diff --git a/.gitignore b/.gitignore index 2cf7a5b..86f6f32 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,7 @@ CLAUDE.md Config/Secrets.xcconfig ios-maps-sdk/ !.github/ +heresdk +samples/MapConductorSampleApp/build_sim +samples/MapConductorSampleApp/build_device +samples/MapConductorSampleApp/UITests diff --git a/.gitmodules b/.gitmodules index 2c4618b..0e6145f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,18 @@ [submodule "ios-for-arcgis"] path = ios-for-arcgis url = git@github.com:MapConductor/ios-for-arcgis.git +[submodule "ios-for-here"] + path = ios-for-here + url = git@github.com:MapConductor/ios-for-here.git +[submodule "ios-geojson-layer"] + path = ios-geojson-layer + url = git@github.com:MapConductor/ios-geojson-layer.git +[submodule "ios-for-maptiler"] + path = ios-for-maptiler + url = git@github.com:MapConductor/ios-for-maptiler.git +[submodule "ios-for-tomtom"] + path = ios-for-tomtom + url = git@github.com:MapConductor/ios-for-tomtom.git +[submodule "ios-for-longdo"] + path = ios-for-longdo + url = git@github.com:MapConductor/ios-for-longdo.git diff --git a/README.es-419.md b/README.es-419.md new file mode 100644 index 0000000..cdfa926 --- /dev/null +++ b/README.es-419.md @@ -0,0 +1,390 @@ +# MapConductor iOS SDK + +- [English Doc](./README.md) +- [Japanese Doc](./README.ja.md) + +**Una sola API de mapas para iOS que funciona con múltiples proveedores de mapas.** + +MapConductor iOS SDK es una librería de mapas para iOS que te permite trabajar con múltiples SDKs de mapas a través de una API única y consistente basada en SwiftUI. + +En lugar de escribir código de mapas distinto para Google Maps, Mapbox, Apple MapKit, ArcGIS, HERE Maps y MapLibre, MapConductor ofrece abstracciones compartidas para mapas, estado de cámara, marcadores, formas, superposiciones y funciones avanzadas de mapas. + +Escribe tu interfaz de mapa una sola vez. +Elige el proveedor de mapas que mejor se adapte a tu producto. + +--- + +## ¿Por qué MapConductor? + +El desarrollo de mapas para móviles suele quedar fuertemente acoplado a un SDK de mapas específico. Cada proveedor tiene su propio diseño de API, modelo de ciclo de vida, comportamiento de renderizado y conjunto de funciones. Esto dificulta cambiar de proveedor, dar soporte a varios backends de mapas o mantener limpio el código relacionado con mapas en una aplicación moderna con SwiftUI. + +MapConductor resuelve esto proporcionando una capa común sobre los principales SDKs de mapas para iOS. + +Con MapConductor puedes: + +* Usar una API orientada a SwiftUI para la interfaz de mapas +* Cambiar entre los proveedores de mapas compatibles con menos trabajo de reescritura +* Compartir la misma lógica de marcadores, círculos, polilíneas, polígonos y superposiciones +* Construir funciones de mapas independientes del proveedor, como mapas de calor y agrupamiento de marcadores +* Mantener tu código de aplicación enfocado en el comportamiento del mapa, no en las diferencias específicas de cada SDK + +![](docs/src/assets/top-page/es-419-comic-why-mapconductor.jpg) + +--- + +## Proveedores de mapas compatibles + +MapConductor actualmente es compatible con los siguientes proveedores de mapas para iOS: + +| Proveedor | Paquete | Producto | +| ---------------- | ------------------------------ | -------------------------------- | +| Google Maps | [ios-for-googlemaps](https://github.com/MapConductor/ios-for-googlemaps) | `MapConductorForGoogleMaps` | +| Mapbox | [ios-for-mapbox](https://github.com/MapConductor/ios-for-mapbox) | `MapConductorForMapbox` | +| Apple MapKit | [ios-for-mapkit](https://github.com/MapConductor/ios-for-mapkit) | `MapConductorForMapKit` | +| ArcGIS | [ios-for-arcgis](https://github.com/MapConductor/ios-for-arcgis) | `MapConductorForArcGIS` | +| HERE Maps | [ios-for-here](https://github.com/MapConductor/ios-for-here) | `MapConductorForHERE` | +| MapLibre | [ios-for-maplibre](https://github.com/MapConductor/ios-for-maplibre) | `MapConductorForMapLibre` | + +Puedes elegir un proveedor para tu app, o estructurar tu código de modo que el proveedor pueda cambiarse más adelante. + +--- + +## Funciones principales + +MapConductor ofrece una API unificada para las funciones comunes de interfaz de mapas y geoespaciales: + +* Componentes de vista de mapa para múltiples proveedores +* Estado de cámara y posición de cámara +* Marcadores +* Iconos de marcador personalizados +* Círculos con radio en metros +* Polilíneas +* Polígonos +* Imágenes de superficie (ground images) +* Capas de teselas ráster (raster tile layers) +* Mapas de calor +* Agrupamiento de marcadores +* Capas GeoJSON +* Tipos de geometría compartidos como `GeoPoint` +* Gestión de estado reactiva para objetos de mapa, construida sobre SwiftUI + +El objetivo no es solo envolver el SDK de cada proveedor, sino también ofrecer un comportamiento consistente, en la medida de lo posible, entre los distintos motores de mapas. + +--- + +## Instalación + +Agrega los paquetes necesarios en Xcode (File → Add Package Dependencies) o directamente en tu `Package.swift`: + +```swift +dependencies: [ + .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "1.0.5"), + .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "1.0.5"), // o el proveedor que elijas + + // Paquetes de funciones opcionales + .package(url: "https://github.com/MapConductor/ios-heatmap", from: "1.0.3"), + .package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "1.0.2"), + .package(url: "https://github.com/MapConductor/ios-geojson-layer", from: "1.0.0"), +], +``` + +Luego agrega los productos a tu target: + +```swift +.target( + name: "YourApp", + dependencies: [ + .product(name: "MapConductorCore", package: "ios-sdk-core"), + .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), + ] +) +``` + +Cada proveedor de mapas puede requerir su propia clave de API, token de acceso, o configuración del proyecto de Xcode (como entradas en el Info.plist). Por favor revisa la guía de configuración del proveedor que estés usando. + +--- + +## Ejemplo básico + +El siguiente ejemplo muestra un mapa simple con SwiftUI que incluye un marcador y un círculo. + + +```swift +import SwiftUI +import MapConductorCore +import MapConductorForMapKit + +struct ContentView: View { + @StateObject private var mapState = MapKitViewState( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6762, longitude: 139.6503), + zoom: 16, + ) + ) + + var body: some View { + MapKitMapView(state: mapState, content : { + Marker(position: GeoPoint(latitude: 35.6762, longitude: 139.6503)) + Circle( + center: GeoPoint(latitude: 35.6762, longitude: 139.6503), + radiusMeters: 500, + strokeColor: UIColor.red, + strokeWidth: 2, + fillColor: UIColor.blue.withAlphaComponent(0.5), + ) + }) + } +} +``` + +Este ejemplo usa Google Maps, pero los objetos del mapa están escritos usando conceptos de MapConductor. La misma lógica de superposiciones se puede adaptar a otros proveedores compatibles. + +![](docs/src/assets/top-page/basic-sample.png) + +--- + +## Cambiar de proveedor de mapas + +![](docs/src/assets/top-page/unified-map-view.png) + +Una de las ideas principales detrás de MapConductor es que tus superposiciones de mapa no deberían tener que reescribirse cuando cambias de proveedor de mapas. + +Simplemente cambia el estado y la vista — todas las superposiciones funcionan sin cambios: + + +- MapKit + + ```swift + struct SimpleMap: View { + + @StateObject private var mapKitState = MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, + cameraPosition: StoreDemoData.initCameraPosition + ) + + var body: some View { + MapKitMapView( + state: mapKitState, + onMapClick: { geoPoint -> + NSLog("clicked at \(geoPoint)") + } + content: { + + Marker( + state: MarkerState( + position = GeoPoint(35.6762, 139.6503), + ) + ) + + Circle( + state: CircleState( + center = GeoPoint(35.6762, 139.6503), + radiusMeters = 500.0, + fillColor = Color.Green.copy(alpha = 0.5f), + strokeColor = Color.Blue, + strokeWidth = 3.dp, + ) + ) + }) + } + } + ``` + +-
+ MapLibre (Toca para abrir) + + ```swift + val initCameraPosition = MapCameraPosition(...) + + val googleMapState = rememberGoogleMapViewState( + cameraPosition = initCameraPosition, + mapDesign = GoogleMapDesign.Normal, + ) + + GoogleMapView(state = googleMapState) { + MapContent() + } + ``` +
+ +-
+ Google Maps (Toca para abrir) + + ```swift + val initCameraPosition = MapCameraPosition(...) + + val googleMapState = rememberGoogleMapViewState( + cameraPosition = initCameraPosition, + mapDesign = GoogleMapDesign.Normal, + ) + + GoogleMapView(state = googleMapState) { + MapContent() + } + ``` +
+ +-
+ Mapbox (Toca para abrir) + + ```swift + val initCameraPosition = MapCameraPosition(...) + + val mapboxMapState = rememberMapboxMapViewState( + cameraPosition = initCameraPosition, + mapDesign = MapboxMapDesign.Standard, + ) + + MapboxMapView(state = mapboxMapState) { + MapContent() + } + ``` +
+ +-
+ HERE (Toca para abrir) + + ```swift + val initCameraPosition = MapCameraPosition(...) + + val hereMapState = rememberHereMapViewState( + cameraPosition = initCameraPosition, + mapDesign = HereMapDesign.NormalDay, + ) + + HereMapView(state = hereMapState) { + MapContent() + } + ``` +
+ +-
+ ArcGIS 2D (Toca para abrir) + + ```swift + val initCameraPosition = MapCameraPosition(...) + + val arcgisMapState = rememberArcGISMapViewState( + cameraPosition = initCameraPosition, + mapDesign = ArcGISDesign.Streets, + ) + + ArcGISMapView2D(state = arcgisMapState) { + MapContent() + } + ``` +
+ +-
+ ArcGIS 3D (Toca para abrir) + + ```swift + val initCameraPosition = MapCameraPosition(...) + + val arcgisMapState = rememberArcGISMapViewState( + cameraPosition = initCameraPosition, + mapDesign = ArcGISDesign.Streets, + ) + + ArcGISMapView(state = arcgisMapState) { + MapContent() + } + ``` +
+ +```swift +// Google Maps +GoogleMapView(state: googleMapState) { /* overlays */ } + +// Mapbox +MapboxMapView(state: mapboxState) { /* overlays */ } + +// Apple MapKit +MapKitMapView(state: mapKitState) { /* overlays */ } + +// ArcGIS +ArcGISMapView(state: arcgisState) { /* overlays */ } + +// HERE Maps +HEREMapView(state: hereMapState) { /* overlays */ } + +// MapLibre +MapLibreMapView(state: maplibreState) { /* overlays */ } +``` + +Tu contenido de mapa reutilizable puede incluir marcadores, círculos, polilíneas, polígonos, mapas de calor, agrupaciones u otros componentes de MapConductor, sin importar qué vista de proveedor los renderice. + +Aún se requiere una configuración específica por proveedor, pero la interfaz de mapas a nivel de aplicación puede ser mucho más portable. + +--- + +## Resumen de módulos + +| Módulo | Paquete | Producto | Descripción | +| ----------------------------- | --------------------------------- | --------------------------------- | --------------------------------------------------------------------- | +| `ios-sdk-core` | `mapconductor-core` | `MapConductorCore` | Abstracciones principales, tipos de geometría, estado de superposiciones | +| `ios-for-googlemaps` | `mapconductor-for-googlemaps` | `MapConductorForGoogleMaps` | Implementación del proveedor Google Maps | +| `ios-for-mapbox` | `ios-for-mapbox` | `MapConductorForMapbox` | Implementación del proveedor Mapbox | +| `ios-for-mapkit` | `mapconductor-for-mapkit` | `MapConductorForMapKit` | Implementación del proveedor Apple MapKit | +| `ios-for-arcgis` | `mapconductor-for-arcgis` | `MapConductorForArcGIS` | Implementación del proveedor ArcGIS | +| `ios-for-here` | `mapconductor-for-here` | `MapConductorForHERE` | Implementación del proveedor HERE Maps | +| `ios-for-maplibre` | `mapconductor-for-maplibre` | `MapConductorForMapLibre` | Implementación del proveedor MapLibre | +| `ios-heatmap` | `mapconductor-heatmap` | `MapConductorHeatmap` | Superposición de mapa de calor independiente del proveedor | +| `ios-marker-cluster` | `mapconductor-marker-cluster` | `MapConductorMarkerCluster` | Soporte de agrupamiento de marcadores | +| `ios-geojson-layer` | `mapconductor-geojson-layer` | `MapConductorGeoJSONLayer` | Soporte de capas GeoJSON | + +--- + +## Estado de las funciones + +| Función | Google Maps | Mapbox | MapKit | ArcGIS | HERE Maps | MapLibre | +| ------------------ | -----------: | --------: | --------: | --------: | --------: | --------: | +| Map | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Marker | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Circle | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Polyline | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Polygon | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Ground Image | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Heatmap | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Marker Clustering | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Raster Tile Layer | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Vector Tile Layer | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | + +MapConductor está en desarrollo activo. Consulta la documentación y las notas de la versión para conocer el comportamiento y las limitaciones más recientes de cada proveedor. + +--- + +## ¿Para quién es esto? + +MapConductor te resulta útil si estás: + +* Construyendo una app de iOS con SwiftUI y mapas +* Evaluando múltiples proveedores de mapas +* Planeando una posible migración de un SDK de mapas a otro +* Manteniendo funciones de mapas para distintos requisitos de clientes o regiones +* Construyendo componentes de interfaz de mapas reutilizables +* Buscando una capa de abstracción de código abierto para mapas móviles + +Es especialmente útil cuando quieres que tu código de aplicación describa qué debe aparecer en el mapa, en lugar de cómo espera cada SDK de proveedor que se implemente esa función. + +--- + +## Ejemplos + +Consulta [samples/MapConductorSampleApp](samples/MapConductorSampleApp) para ver una app de demostración completa que muestra todos los módulos. + +--- + +## Estado del proyecto + +MapConductor iOS SDK ya está publicado y se encuentra en desarrollo activo. + +El proyecto busca hacer que el desarrollo de mapas sea más flexible, portable y amigable con SwiftUI en los principales proveedores de mapas para iOS. Algunas funciones avanzadas pueden seguir siendo experimentales o presentar diferencias específicas por proveedor. + +Los comentarios, reportes de errores y contribuciones son bienvenidos. + +--- + +## Licencia + +MapConductor iOS SDK se publica bajo la Licencia Apache 2.0. diff --git a/README.ja.md b/README.ja.md new file mode 100644 index 0000000..8d976a5 --- /dev/null +++ b/README.ja.md @@ -0,0 +1,248 @@ +# MapConductor iOS SDK + +- [English Doc](./README.md) +- [Spanish Doc](./README.es-419.md) + +**複数のマッププロバイダーに対応する、ひとつのiOSマップAPI。** + +MapConductor iOS SDKは、SwiftUIベースの単一で一貫したAPIを通じて複数のマップSDKを扱えるようにする、iOS向けマッピングライブラリです。 + +Google Maps、Mapbox、Apple MapKit、ArcGIS、HERE Maps、MapLibreごとに異なるマップコードを書く代わりに、MapConductorはマップ、カメラの状態、マーカー、図形、オーバーレイ、高度なマップ機能のための共通の抽象化を提供します。 + +マップUIは一度だけ書きます。 +プロダクトに合ったマッププロバイダーを選びましょう。 + +--- + +## なぜMapConductorなのか? + +モバイルのマップ開発は、特定のマップSDKに強く依存してしまうことがよくあります。各プロバイダーは独自のAPI設計、ライフサイクルモデル、レンダリングの挙動、機能セットを持っています。これにより、プロバイダーの切り替えや複数のマップバックエンドのサポート、最新のSwiftUIアプリケーションにおけるマップ関連コードのクリーンな維持が難しくなります。 + +MapConductorは、主要なiOSマップSDKの上に共通レイヤーを提供することでこれを解決します。 + +MapConductorを使うと、以下のことができます: + +* マップUIにSwiftUIファーストのAPIを使う +* 少ない書き換え作業でサポートされているマッププロバイダーを切り替える +* マーカー、円、ポリライン、ポリゴン、オーバーレイのロジックを共有する +* プロバイダーに依存しないヒートマップやマーカークラスタリングなどのマップ機能を構築する +* アプリケーションコードをSDK固有の差異ではなくマップの挙動に集中させる + +![](docs/src/assets/top-page/ja-comic-why-mapconductor.jpg) + +--- + +## サポートされているマッププロバイダー + +MapConductorは現在、以下のiOSマッププロバイダーをサポートしています: + +| プロバイダー | パッケージ | プロダクト | +| ---------------- | ------------------------------ | ------------------------------- | +| Google Maps | [ios-for-googlemaps](https://github.com/MapConductor/ios-for-googlemaps) | `MapConductorForGoogleMaps` | +| Mapbox | [ios-for-mapbox](https://github.com/MapConductor/ios-for-mapbox) | `MapConductorForMapbox` | +| Apple MapKit | [ios-for-mapkit](https://github.com/MapConductor/ios-for-mapkit) | `MapConductorForMapKit` | +| ArcGIS | [ios-for-arcgis](https://github.com/MapConductor/ios-for-arcgis) | `MapConductorForArcGIS` | +| HERE Maps | [ios-for-here](https://github.com/MapConductor/ios-for-here) | `MapConductorForHERE` | +| MapLibre | [ios-for-maplibre](https://github.com/MapConductor/ios-for-maplibre) | `MapConductorForMapLibre` | + +アプリ用に1つのプロバイダーを選んでもよいですし、後でプロバイダーを変更できるようにコードを構成することもできます。 + +--- + +## コア機能 + +MapConductorは、一般的なマップUIおよび地理空間機能に対して統一されたAPIを提供します: + +* 複数プロバイダー向けのマップビューコンポーネント +* カメラの状態とカメラ位置 +* マーカー +* カスタムマーカーアイコン +* メートル単位の半径を持つ円 +* ポリライン +* ポリゴン +* グラウンドイメージ +* ラスタータイルレイヤー +* ヒートマップ +* マーカークラスタリング +* GeoJSONレイヤー +* `GeoPoint`などの共有ジオメトリ型 +* SwiftUI上に構築された、マップオブジェクトのためのリアクティブな状態管理 + +目的は各プロバイダーSDKをラップするだけでなく、可能な限り異なるマップエンジン間で一貫した動作を提供することです。 + +--- + +## インストール + +Xcode (File → Add Package Dependencies) または `Package.swift` に直接、必要なパッケージを追加してください。 + +```swift +dependencies: [ + .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "1.0.5"), + .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "1.0.6"), // または選択したプロバイダー + + // オプションの機能パッケージ + .package(url: "https://github.com/MapConductor/ios-heatmap", from: "1.0.3"), + .package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "1.0.2"), + .package(url: "https://github.com/MapConductor/ios-geojson-layer", from: "1.0.0"), +], +``` + +次に、ターゲットにプロダクトを追加します。 + +```swift +.target( + name: "YourApp", + dependencies: [ + .product(name: "MapConductorCore", package: "ios-sdk-core"), + .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), + ] +) +``` + +各マッププロバイダーは、それぞれ独自のAPIキー、アクセストークン、またはXcodeプロジェクトの設定(Info.plistのエントリなど)が必要な場合があります。使用するプロバイダーのセットアップガイドを確認してください。 + +--- + +## 基本的な例 + +以下の例は、マーカーと円を含むシンプルなSwiftUIマップを示しています。 + +```swift +import SwiftUI +import MapConductorCore +import MapConductorForMapKit + +struct ContentView: View { + @StateObject private var mapState = MapKitViewState( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6762, longitude: 139.6503), + zoom: 16, + ) + ) + + var body: some View { + MapKitMapView(state: mapState, content : { + Marker(position: GeoPoint(latitude: 35.6762, longitude: 139.6503)) + Circle( + center: GeoPoint(latitude: 35.6762, longitude: 139.6503), + radiusMeters: 500, + strokeColor: UIColor.red, + strokeWidth: 2, + fillColor: UIColor.blue.withAlphaComponent(0.5), + ) + }) + } +} +``` + +この例ではMapKitを使用していますが、マップオブジェクトはMapConductorの概念を使って書かれています。同じオーバーレイのロジックを他のサポートされているプロバイダーに適用できます。 + +![](docs/src/assets/top-page/basic-sample.png) + +--- + +## マッププロバイダーの切り替え + +![](docs/src/assets/top-page/unified-map-view.png) + +MapConductorの主な考え方の1つは、マッププロバイダーを変更してもマップオーバーレイを書き直す必要がないということです。 + +stateとviewを変えるだけで、すべてのオーバーレイはそのまま動作します: + +```swift +// Google Maps +GoogleMapView(state: googleMapState) { /* overlays */ } + +// Mapbox +MapboxMapView(state: mapboxState) { /* overlays */ } + +// Apple MapKit +MapKitMapView(state: mapKitState) { /* overlays */ } + +// ArcGIS +ArcGISMapView(state: arcgisState) { /* overlays */ } + +// HERE Maps +HEREMapView(state: hereMapState) { /* overlays */ } + +// MapLibre +MapLibreMapView(state: maplibreState) { /* overlays */ } +``` + +再利用可能なマップコンテンツには、どのプロバイダーのビューがレンダリングするかにかかわらず、マーカー、円、ポリライン、ポリゴン、ヒートマップ、クラスター、その他のMapConductorコンポーネントを含めることができます。 + +プロバイダー固有のセットアップは依然として必要ですが、アプリケーションレベルのマップUIはより高いポータビリティを維持できます。 + +--- + +## モジュール概要 + +| モジュール | 説明 | +| ------------------------------------------------------- | ------------------------------------------------ | +| https://github.com/MapConductor/ios-sdk-core | コアの抽象化、ジオメトリ型、オーバーレイの状態 | +| https://github.com/MapConductor/ios-for-googlemaps | Google Mapsプロバイダー実装 | +| https://github.com/MapConductor/ios-for-mapbox | Mapboxプロバイダー実装 | +| https://github.com/MapConductor/ios-for-mapkit | Apple MapKitプロバイダー実装 | +| https://github.com/MapConductor/ios-for-arcgis | ArcGISプロバイダー実装 | +| https://github.com/MapConductor/ios-for-here | HERE Mapsプロバイダー実装 | +| https://github.com/MapConductor/ios-for-maplibre | MapLibreプロバイダー実装 | +| https://github.com/MapConductor/ios-heatmap | プロバイダーに依存しないヒートマップオーバーレイ | +| https://github.com/MapConductor/ios-marker-cluster | マーカークラスタリングのサポート | +| https://github.com/MapConductor/ios-geojson-layer | GeoJSONレイヤーのサポート | + +--- + +## 機能対応状況 + +| 機能 | Google Maps | Mapbox | MapKit | ArcGIS | HERE Maps | MapLibre | +| ------------------ | -----------: | --------: | --------: | --------: | --------: | --------: | +| Map | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Marker | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Circle | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Polyline | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Polygon | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Ground Image | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Heatmap | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Marker Clustering | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Raster Tile Layer | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Vector Tile Layer | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | + +MapConductorは活発に開発が進められています。最新のプロバイダー固有の挙動や制限事項については、ドキュメントとリリースノートを確認してください。 + +--- + +## こんな方に向いています + +MapConductorは、以下のような方に役立ちます: + +* SwiftUIとマップを使ったiOSアプリを構築している +* 複数のマッププロバイダーを評価している +* あるマップSDKから別のものへの移行を計画している +* 異なる顧客や地域の要件にわたってマップ機能を保守している +* 再利用可能なマップUIコンポーネントを構築している +* モバイルマップ向けのオープンソースの抽象化レイヤーを探している + +各プロバイダーSDKがその機能をどのように実装することを期待しているかではなく、マップに何を表示すべきかをアプリケーションコードで記述したい場合に特に役立ちます。 + +--- + +## サンプル + +すべてのモジュールを紹介するフル機能のデモアプリについては、[samples/MapConductorSampleApp](samples/MapConductorSampleApp)を参照してください。 + +--- + +## プロジェクトの状況 + +MapConductor iOS SDKはリリース済みで、活発に開発が進められています。 + +このプロジェクトは、主要なiOSマッププロバイダーにわたって、マップ開発をより柔軟でポータブル、かつSwiftUIフレンドリーにすることを目指しています。一部の高度な機能は実験的であったり、プロバイダー固有の差異がある場合があります。 + +フィードバック、Issue、コントリビューションを歓迎します。 + +--- + +## ライセンス + +MapConductor iOS SDKはApache License 2.0のもとでリリースされています。 diff --git a/README.md b/README.md index b6ede51..a69f21d 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,110 @@ # MapConductor iOS SDK -A unified mapping library that provides a common API for multiple map providers including Google Maps, Mapbox, MapKit, ArcGIS, and MapLibre. Write once, deploy across all major mapping platforms. +## XCFramework distribution -## Features +Build the Core, extension, Google Maps, and MapLibre XCFrameworks with: -- **Multi-Provider Support**: Seamlessly switch between Google Maps, Mapbox, MapKit, ArcGIS, and MapLibre with a single API -- **Unified Interface**: Common abstractions for markers, circles, polylines, polygons, ground overlays, heatmaps, and marker clustering -- **SwiftUI**: Modern iOS UI framework integration +```shell +scripts/build-xcframeworks.sh +``` + +Pass module names to build a subset, or `--all` to include every provider: + +```shell +scripts/build-xcframeworks.sh core geojson +scripts/build-xcframeworks.sh --all +``` + +Artifacts are written to `build/xcframeworks/output`. Every MapConductor module is a +separate dynamic framework built with `BUILD_LIBRARY_FOR_DISTRIBUTION=YES` so the Core +framework is linked only once. Google Maps is statically linked by its upstream Swift +Package; MapLibre remains a dynamic external dependency. + +- [Japanese Doc](./README.ja.md) +- [Spanish Doc](./README.es-419.md) + +**One iOS map API for multiple map providers.** + +MapConductor iOS SDK is a mapping library for iOS that lets you work with multiple map SDKs through a single, consistent SwiftUI API. + +Instead of writing different map code for Google Maps, Mapbox, Apple MapKit, ArcGIS, HERE Maps, and MapLibre, MapConductor provides shared abstractions for maps, camera state, markers, shapes, overlays, and advanced map features. + +Write your map UI once. +Choose the map provider that fits your product. + +--- + +## Why MapConductor? + +Mobile map development often becomes tightly coupled to a specific map SDK. Each provider has its own API design, lifecycle model, rendering behavior, and feature set. This makes it hard to switch providers, support multiple map backends, or keep map-related code clean in a modern SwiftUI application. + +MapConductor solves this by providing a common layer on top of major iOS map SDKs. + +With MapConductor, you can: -## Module Structure +* Use a SwiftUI-first API for map UI +* Switch between supported map providers with less rewrite work +* Share the same marker, circle, polyline, polygon, and overlay logic +* Build provider-independent map features such as heatmaps and marker clustering +* Keep your application code focused on map behavior, not SDK-specific differences -| Module | Package | Product | Description | -|---|---|---|---| -| `ios-sdk-core` | `mapconductor-core` | `MapConductorCore` | Core abstractions, geometry types, overlay states | -| `ios-for-googlemaps` | `mapconductor-for-googlemaps` | `MapConductorForGoogleMaps` | Google Maps implementation | -| `ios-for-mapbox` | `ios-for-mapbox` | `MapConductorForMapbox` | Mapbox implementation | -| `ios-for-mapkit` | `mapconductor-for-mapkit` | `MapConductorForMapKit` | Apple MapKit implementation | -| `ios-for-arcgis`(creating) | `mapconductor-for-arcgis` | `MapConductorForArcGIS` | ArcGIS implementation | -| `ios-for-maplibre` | `mapconductor-for-maplibre` | `MapConductorForMapLibre` | MapLibre implementation | -| `ios-heatmap` | `mapconductor-heatmap` | `MapConductorHeatmap` | Map-provider-agnostic heatmap overlay | -| `ios-marker-cluster` | `mapconductor-marker-cluster` | `MapConductorMarkerCluster` | Automatic marker clustering across all providers | +![](docs/src/assets/top-page/en-comic-why-mapconductor.jpg) -## Quick Start +--- -### 1. Add Dependencies +## Supported Map Providers -Add the required packages in Xcode (File → Add Package Dependencies) or in your `Package.swift`: +MapConductor currently supports the following iOS map providers: + +| Provider | Package | Product | +| ---------------- | ------------------------------ | ------------------------------- | +| Google Maps | [ios-for-googlemaps](https://github.com/MapConductor/ios-for-googlemaps) | `MapConductorForGoogleMaps` | +| Mapbox | [ios-for-mapbox](https://github.com/MapConductor/ios-for-mapbox) | `MapConductorForMapbox` | +| Apple MapKit | [ios-for-mapkit](https://github.com/MapConductor/ios-for-mapkit) | `MapConductorForMapKit` | +| ArcGIS | [ios-for-arcgis](https://github.com/MapConductor/ios-for-arcgis) | `MapConductorForArcGIS` | +| HERE Maps | [ios-for-here](https://github.com/MapConductor/ios-for-here) | `MapConductorForHERE` | +| MapLibre | [ios-for-maplibre](https://github.com/MapConductor/ios-for-maplibre) | `MapConductorForMapLibre` | + +You can choose one provider for your app, or structure your code so that the provider can be changed later. + +--- + +## Core Features + +MapConductor provides a unified API for common map UI and geospatial features: + +* Map view components for multiple providers +* Camera state and camera position +* Markers +* Custom marker icons +* Circles with meter-based radius +* Polylines +* Polygons +* Ground images +* Raster tile layers +* Heatmaps +* Marker clustering +* GeoJSON layers +* Shared geometry types such as `GeoPoint` +* Reactive state management for map objects, built on SwiftUI + +The goal is not only to wrap each provider SDK, but also to provide consistent behavior where possible across different map engines. + +--- + +## Installation + +Add the required packages in Xcode (File → Add Package Dependencies) or directly in your `Package.swift`: ```swift dependencies: [ - .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "1.0.0"), - .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "1.0.0"), // or your chosen provider + .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "1.0.5"), + .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "1.0.5"), // or your chosen provider + + // Optional feature packages + .package(url: "https://github.com/MapConductor/ios-heatmap", from: "1.0.3"), + .package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "1.0.2"), + .package(url: "https://github.com/MapConductor/ios-geojson-layer", from: "1.0.0"), ], ``` @@ -46,34 +120,54 @@ Then add the products to your target: ) ``` -### 2. Basic Usage +Each map provider may require its own API key, access token, or Xcode project configuration (such as Info.plist entries). Please check the setup guide for the provider you are using. + +--- + +## Basic Example + +The following example shows a simple SwiftUI map with a marker and a circle. + ```swift import SwiftUI import MapConductorCore -import MapConductorForGoogleMaps +import MapConductorForMapKit struct ContentView: View { - @StateObject private var mapState = GoogleMapViewState( + @StateObject private var mapState = MapKitViewState( cameraPosition: MapCameraPosition( position: GeoPoint(latitude: 35.6762, longitude: 139.6503), - zoom: 12 + zoom: 16, ) ) var body: some View { - GoogleMapView(state: mapState) { + MapKitMapView(state: mapState, content : { Marker(position: GeoPoint(latitude: 35.6762, longitude: 139.6503)) Circle( center: GeoPoint(latitude: 35.6762, longitude: 139.6503), - radiusMeters: 500 + radiusMeters: 500, + strokeColor: UIColor.red, + strokeWidth: 2, + fillColor: UIColor.blue.withAlphaComponent(0.5), ) - } + }) } } ``` -### 3. Switch Map Providers +This example uses Google Maps, but the map objects are written using MapConductor concepts. The same overlay logic can be adapted to other supported providers. + +![](docs/src/assets/top-page/basic-sample.png) + +--- + +## Switching Map Providers + +![](docs/src/assets/top-page/unified-map-view.png) + +One of the main ideas behind MapConductor is that your map overlays should not have to be rewritten when you change map providers. Simply change the state and view — all overlays work unchanged: @@ -87,28 +181,89 @@ MapboxMapView(state: mapboxState) { /* overlays */ } // Apple MapKit MapKitMapView(state: mapKitState) { /* overlays */ } -// ArcGIS (still working on) +// ArcGIS ArcGISMapView(state: arcgisState) { /* overlays */ } +// HERE Maps +HEREMapView(state: hereMapState) { /* overlays */ } + // MapLibre MapLibreMapView(state: maplibreState) { /* overlays */ } ``` -## Feature Implementation Status - -| | Google Maps | Mapbox | MapKit | ArcGIS | MapLibre | -|---------------------|-------------|----------|----------|----------|----------| -| Map | ☑ | ☑ | ☑ | ☑ | ☑ | -| Marker | ☑ | ☑ | ☑ | ☑ | ☑ | -| Circle | ☑ | ☑ | ☑ | ☑ | ☑ | -| Polyline | ☑ | ☑ | ☑ | ☑ | ☑ | -| Polygon | ☑ | ☑ | ☑ | ☑ | ☑ | -| GroundImage | ☑ | ☑ | ☑ | ☑ | ☑ | -| Heatmap | ☑ | ☑ | ☑ | ☑ | ☑ | -| Marker Clustering | ☑ | ☑ | ☑ | ☑ | ☑ | -| RasterTileLayer | ☑ | ☑ | ☑ | ☑ | ☑ | -| VectorTileLayer | ☐ | ☐ | ☐ | ☐ | ☐ | +Your reusable map content can contain markers, circles, polylines, polygons, heatmaps, clusters, or other MapConductor components, regardless of which provider view renders them. + +Provider-specific setup is still required, but your application-level map UI can stay much more portable. + +--- + +## Module Overview + +| Module | Package | Product | Description | +| ----------------------------- | --------------------------------- | --------------------------------- | --------------------------------------------------------------------- | +| `ios-sdk-core` | `mapconductor-core` | `MapConductorCore` | Core abstractions, geometry types, overlay states | +| `ios-for-googlemaps` | `mapconductor-for-googlemaps` | `MapConductorForGoogleMaps` | Google Maps provider implementation | +| `ios-for-mapbox` | `ios-for-mapbox` | `MapConductorForMapbox` | Mapbox provider implementation | +| `ios-for-mapkit` | `mapconductor-for-mapkit` | `MapConductorForMapKit` | Apple MapKit provider implementation | +| `ios-for-arcgis` | `mapconductor-for-arcgis` | `MapConductorForArcGIS` | ArcGIS provider implementation | +| `ios-for-here` | `mapconductor-for-here` | `MapConductorForHERE` | HERE Maps provider implementation | +| `ios-for-maplibre` | `mapconductor-for-maplibre` | `MapConductorForMapLibre` | MapLibre provider implementation | +| `ios-heatmap` | `mapconductor-heatmap` | `MapConductorHeatmap` | Provider-independent heatmap overlay | +| `ios-marker-cluster` | `mapconductor-marker-cluster` | `MapConductorMarkerCluster` | Marker clustering support | +| `ios-geojson-layer` | `mapconductor-geojson-layer` | `MapConductorGeoJSONLayer` | GeoJSON layer support | + +--- + +## Feature Status + +| Feature | Google Maps | Mapbox | MapKit | ArcGIS | HERE Maps | MapLibre | +| ------------------ | -----------: | --------: | --------: | --------: | --------: | --------: | +| Map | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Marker | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Circle | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Polyline | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Polygon | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Ground Image | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Heatmap | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Marker Clustering | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Raster Tile Layer | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ | +| Vector Tile Layer | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | + +MapConductor is actively developed. Please check the documentation and release notes for the latest provider-specific behavior and limitations. + +--- + +## Who Is This For? + +MapConductor is useful if you are: + +* Building an iOS app with SwiftUI and maps +* Evaluating multiple map providers +* Planning a possible migration from one map SDK to another +* Maintaining map features across different customer or regional requirements +* Building reusable map UI components +* Looking for an open-source abstraction layer for mobile maps + +It is especially helpful when you want your application code to describe what should appear on the map, rather than how each provider SDK expects that feature to be implemented. + +--- ## Samples See [samples/MapConductorSampleApp](samples/MapConductorSampleApp) for a full-featured demo app showcasing all modules. + +--- + +## Project Status + +MapConductor iOS SDK is released and under active development. + +The project aims to make map development more flexible, portable, and SwiftUI-friendly across major iOS map providers. Some advanced features may still be experimental or may have provider-specific differences. + +Feedback, issues, and contributions are welcome. + +--- + +## License + +MapConductor iOS SDK is released under the Apache License 2.0. diff --git a/docs/astro.config.ts b/docs/astro.config.ts index 5aa9c4d..0551cde 100644 --- a/docs/astro.config.ts +++ b/docs/astro.config.ts @@ -62,6 +62,8 @@ export default defineConfig({ { slug: 'setup' }, { slug: 'setup/google-maps' }, { slug: 'setup/mapbox' }, + { slug: 'setup/here-maps' }, + { slug: 'setup/arcgis' }, { slug: 'setup/mapkit' }, { slug: 'setup/maplibre' }, ], @@ -118,6 +120,9 @@ export default defineConfig({ { slug: 'mapviewholder' }, { slug: 'mapviewholder/googlemaps' }, { slug: 'mapviewholder/mapbox' }, + { slug: 'mapviewholder/here-maps' }, + { slug: 'mapviewholder/arcgis' }, + { slug: 'mapviewholder/arcgis-2d' }, { slug: 'mapviewholder/mapkit' }, { slug: 'mapviewholder/maplibre' }, ], @@ -142,6 +147,7 @@ export default defineConfig({ items: [ { slug: 'experimental/heatmap' }, { slug: 'experimental/marker-clustering' }, + { slug: 'experimental/geojson-layer' }, ], }, ], diff --git a/docs/src/assets/top-page/basic-sample.png b/docs/src/assets/top-page/basic-sample.png new file mode 100644 index 0000000..e45bfdb Binary files /dev/null and b/docs/src/assets/top-page/basic-sample.png differ diff --git a/docs/src/assets/top-page/circle.png b/docs/src/assets/top-page/circle.png new file mode 100644 index 0000000..f5ce423 Binary files /dev/null and b/docs/src/assets/top-page/circle.png differ diff --git a/docs/src/assets/top-page/en-comic-why-mapconductor.jpg b/docs/src/assets/top-page/en-comic-why-mapconductor.jpg new file mode 100644 index 0000000..b67ab15 Binary files /dev/null and b/docs/src/assets/top-page/en-comic-why-mapconductor.jpg differ diff --git a/docs/src/assets/top-page/es-419-comic-why-mapconductor.jpg b/docs/src/assets/top-page/es-419-comic-why-mapconductor.jpg new file mode 100644 index 0000000..549af25 Binary files /dev/null and b/docs/src/assets/top-page/es-419-comic-why-mapconductor.jpg differ diff --git a/docs/src/assets/top-page/info-bubble.png b/docs/src/assets/top-page/info-bubble.png new file mode 100644 index 0000000..5923d43 Binary files /dev/null and b/docs/src/assets/top-page/info-bubble.png differ diff --git a/docs/src/assets/top-page/ja-comic-why-mapconductor.jpg b/docs/src/assets/top-page/ja-comic-why-mapconductor.jpg new file mode 100644 index 0000000..5775f70 Binary files /dev/null and b/docs/src/assets/top-page/ja-comic-why-mapconductor.jpg differ diff --git a/docs/src/assets/top-page/marker-cluster.png b/docs/src/assets/top-page/marker-cluster.png new file mode 100644 index 0000000..f12ae25 Binary files /dev/null and b/docs/src/assets/top-page/marker-cluster.png differ diff --git a/docs/src/assets/top-page/markers.png b/docs/src/assets/top-page/markers.png new file mode 100644 index 0000000..2f53068 Binary files /dev/null and b/docs/src/assets/top-page/markers.png differ diff --git a/docs/src/assets/top-page/polyline.png b/docs/src/assets/top-page/polyline.png new file mode 100644 index 0000000..80b43db Binary files /dev/null and b/docs/src/assets/top-page/polyline.png differ diff --git a/docs/src/assets/top-page/unified-map-view.png b/docs/src/assets/top-page/unified-map-view.png new file mode 100644 index 0000000..10936a0 Binary files /dev/null and b/docs/src/assets/top-page/unified-map-view.png differ diff --git a/docs/src/components/components/circle/CircleBasicExample.astro b/docs/src/components/components/circle/CircleBasicExample.astro new file mode 100644 index 0000000..5f673c3 --- /dev/null +++ b/docs/src/components/components/circle/CircleBasicExample.astro @@ -0,0 +1,32 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; + latitude?: number; + longitude?: number; + radiusMeters?: number; + strokeWidth?: number; +} + +const { + commentForClick = "Clicked at", + latitude = 35.6812, + longitude = 139.7671, + radiusMeters = 1000, + strokeWidth = 2, +} = Astro.props; + +const code = `Circle( + center: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + radiusMeters: ${radiusMeters}, + strokeColor: .red, + strokeWidth: ${strokeWidth}, + fillColor: UIColor.red.withAlphaComponent(0.2), + onClick: { event in + print("${commentForClick} \\(event.clicked)") + } +)`; +--- + + diff --git a/docs/src/components/components/circle/CircleClickExample.astro b/docs/src/components/components/circle/CircleClickExample.astro new file mode 100644 index 0000000..ba3cfad --- /dev/null +++ b/docs/src/components/components/circle/CircleClickExample.astro @@ -0,0 +1,28 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; + latitude?: number; + longitude?: number; + radiusMeters?: number; +} + +const { + commentForClick = "Circle clicked at", + latitude = 35.6812, + longitude = 139.7671, + radiusMeters = 750, +} = Astro.props; + +const code = `Circle( + center: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + radiusMeters: ${radiusMeters}, + clickable: true, + onClick: { event in + print("${commentForClick} \\(event.clicked)") + } +)`; +--- + + diff --git a/docs/src/components/components/circle/CircleInitializer.astro b/docs/src/components/components/circle/CircleInitializer.astro new file mode 100644 index 0000000..84dac50 --- /dev/null +++ b/docs/src/components/components/circle/CircleInitializer.astro @@ -0,0 +1,19 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `Circle( + center: GeoPointProtocol, + radiusMeters: Double, + geodesic: Bool = true, + clickable: Bool = true, + strokeColor: UIColor = .red, + strokeWidth: Double = 1.0, + fillColor: UIColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5), + id: String? = nil, + zIndex: Int? = nil, + extra: Any? = nil, + onClick: OnCircleEventHandler? = nil +)`; +--- + + diff --git a/docs/src/components/components/circle/CircleStateExample.astro b/docs/src/components/components/circle/CircleStateExample.astro new file mode 100644 index 0000000..374b4c9 --- /dev/null +++ b/docs/src/components/components/circle/CircleStateExample.astro @@ -0,0 +1,29 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + initialRadius?: number; + updatedRadius?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + initialRadius = 500, + updatedRadius = 1000, +} = Astro.props; + +const code = `let state = CircleState( + center: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + radiusMeters: ${initialRadius} +) + +Circle(state: state) + +state.radiusMeters = ${updatedRadius} +state.fillColor = UIColor.blue.withAlphaComponent(0.2)`; +--- + + diff --git a/docs/src/components/components/groundimage/GroundImageBasicExample.astro b/docs/src/components/components/groundimage/GroundImageBasicExample.astro new file mode 100644 index 0000000..28d0df0 --- /dev/null +++ b/docs/src/components/components/groundimage/GroundImageBasicExample.astro @@ -0,0 +1,32 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; + opacity?: number; + tileSize?: number; +} + +const { + commentForClick = "Ground image clicked", + opacity = 0.7, + tileSize = 512, +} = Astro.props; + +const code = `let bounds = GeoRectBounds( + southWest: GeoPoint(latitude: 35.67, longitude: 139.74), + northEast: GeoPoint(latitude: 35.69, longitude: 139.76) +) + +GroundImage( + bounds: bounds, + image: UIImage(named: "overlay")!, + opacity: ${opacity}, + tileSize: ${tileSize}, + onClick: { event in + print("${commentForClick}: \\(String(describing: event.clicked))") + } +)`; +--- + + diff --git a/docs/src/components/components/groundimage/GroundImageClickExample.astro b/docs/src/components/components/groundimage/GroundImageClickExample.astro new file mode 100644 index 0000000..bf8ab0f --- /dev/null +++ b/docs/src/components/components/groundimage/GroundImageClickExample.astro @@ -0,0 +1,21 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; +} + +const { + commentForClick = "Clicked image", +} = Astro.props; + +const code = `GroundImage( + bounds: bounds, + image: image, + onClick: { event in + print("${commentForClick}: \\(event.state.id)") + } +)`; +--- + + diff --git a/docs/src/components/components/groundimage/GroundImageInitializer.astro b/docs/src/components/components/groundimage/GroundImageInitializer.astro new file mode 100644 index 0000000..9b00bba --- /dev/null +++ b/docs/src/components/components/groundimage/GroundImageInitializer.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `GroundImage( + bounds: GeoRectBounds, + image: UIImage, + opacity: Double = 1.0, + tileSize: Int = 512, + id: String? = nil, + extra: Any? = nil, + onClick: OnGroundImageEventHandler? = nil +)`; +--- + + diff --git a/docs/src/components/components/groundimage/GroundImageOpacityExample.astro b/docs/src/components/components/groundimage/GroundImageOpacityExample.astro new file mode 100644 index 0000000..7aad3e7 --- /dev/null +++ b/docs/src/components/components/groundimage/GroundImageOpacityExample.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + opacity?: number; +} + +const { + opacity = 0.35, +} = Astro.props; + +const code = `state.opacity = ${opacity}`; +--- + + diff --git a/docs/src/components/components/groundimage/GroundImageStateExample.astro b/docs/src/components/components/groundimage/GroundImageStateExample.astro new file mode 100644 index 0000000..fec37ed --- /dev/null +++ b/docs/src/components/components/groundimage/GroundImageStateExample.astro @@ -0,0 +1,22 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + initialOpacity?: number; + updatedOpacity?: number; +} + +const { + initialOpacity = 0.5, + updatedOpacity = 1.0, +} = Astro.props; + +const code = `let state = GroundImageState(bounds: bounds, image: image, opacity: ${initialOpacity}) + +GroundImage(state: state) + +state.opacity = ${updatedOpacity} +state.bounds = updatedBounds`; +--- + + diff --git a/docs/src/components/components/infobubble/InfoBubbleBasicExample.astro b/docs/src/components/components/infobubble/InfoBubbleBasicExample.astro new file mode 100644 index 0000000..3e9be72 --- /dev/null +++ b/docs/src/components/components/infobubble/InfoBubbleBasicExample.astro @@ -0,0 +1,33 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `let markerState = MarkerState( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + extra: "Tokyo Station" +) + +GoogleMapView(state: mapViewState) { + Marker(state: markerState) + + InfoBubble(marker: markerState) { + VStack(alignment: .leading, spacing: 4) { + Text(markerState.extra as? String ?? markerState.id) + .font(.headline) + Text("\\(markerState.position.latitude), \\(markerState.position.longitude)") + .font(.caption) + } + } +}`; +--- + + diff --git a/docs/src/components/components/infobubble/InfoBubbleInitializer.astro b/docs/src/components/components/infobubble/InfoBubbleInitializer.astro new file mode 100644 index 0000000..df2aadc --- /dev/null +++ b/docs/src/components/components/infobubble/InfoBubbleInitializer.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `InfoBubble( + marker: MarkerState, + bubbleColor: Color = .white, + borderColor: Color = .black, + contentPadding: CGFloat = 8.0, + cornerRadius: CGFloat = 4.0, + tailSize: CGFloat = 8.0, + @ViewBuilder content: () -> Content +)`; +--- + + diff --git a/docs/src/components/components/infobubble/InfoBubbleStylingExample.astro b/docs/src/components/components/infobubble/InfoBubbleStylingExample.astro new file mode 100644 index 0000000..d55441e --- /dev/null +++ b/docs/src/components/components/infobubble/InfoBubbleStylingExample.astro @@ -0,0 +1,26 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForLabel?: string; +} + +const { + commentForLabel = "Selected place", +} = Astro.props; + +const code = `InfoBubble( + marker: markerState, + bubbleColor: Color.black.opacity(0.85), + borderColor: .white, + contentPadding: 10, + cornerRadius: 10, + tailSize: 10 +) { + Text("${commentForLabel}") + .font(.headline) + .foregroundColor(.white) +}`; +--- + + diff --git a/docs/src/components/components/mapviewcomponent/MapViewComponentBasicExample.astro b/docs/src/components/components/mapviewcomponent/MapViewComponentBasicExample.astro new file mode 100644 index 0000000..0dd7db9 --- /dev/null +++ b/docs/src/components/components/mapviewcomponent/MapViewComponentBasicExample.astro @@ -0,0 +1,58 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; + commentForMapClick?: string; + commentForCameraMove?: string; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoom = 13, + commentForMapClick = "Map clicked at", + commentForCameraMove = "Camera ended at zoom", +} = Astro.props; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForGoogleMaps + +struct ContentView: View { + @StateObject private var mapViewState = GoogleMapViewState( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} + ) + ) + + var body: some View { + GoogleMapView( + state: mapViewState, + onMapClick: { point in + print("${commentForMapClick} \\(point)") + }, + onCameraMoveEnd: { camera in + print("${commentForCameraMove} \\(camera.zoom)") + } + ) { + Marker( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + icon: DefaultMarkerIcon(label: "T") + ) + + Circle( + center: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + radiusMeters: 500, + fillColor: UIColor.red.withAlphaComponent(0.2) + ) + } + .ignoresSafeArea() + } +}`; +--- + + diff --git a/docs/src/components/components/mapviewcomponent/MapViewLayoutExample.astro b/docs/src/components/components/mapviewcomponent/MapViewLayoutExample.astro new file mode 100644 index 0000000..af8d801 --- /dev/null +++ b/docs/src/components/components/mapviewcomponent/MapViewLayoutExample.astro @@ -0,0 +1,21 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + frameHeight?: number; + cornerRadius?: number; +} + +const { + frameHeight = 320, + cornerRadius = 8, +} = Astro.props; + +const code = `GoogleMapView(state: mapViewState) { + Marker(position: tokyoStation) +} +.frame(height: ${frameHeight}) +.clipShape(RoundedRectangle(cornerRadius: ${cornerRadius}))`; +--- + + diff --git a/docs/src/components/components/mapviewcomponent/MapViewProviderSwitchExample.astro b/docs/src/components/components/mapviewcomponent/MapViewProviderSwitchExample.astro new file mode 100644 index 0000000..10df47a --- /dev/null +++ b/docs/src/components/components/mapviewcomponent/MapViewProviderSwitchExample.astro @@ -0,0 +1,28 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoom = 13, +} = Astro.props; + +const code = `@StateObject private var state = MapboxViewState( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} + ) +) + +MapboxMapView(state: state) { + Marker(position: GeoPoint(latitude: ${latitude}, longitude: ${longitude})) +}`; +--- + + diff --git a/docs/src/components/components/mapviewstate/MapViewStateCameraFullExample.astro b/docs/src/components/components/mapviewstate/MapViewStateCameraFullExample.astro new file mode 100644 index 0000000..f0b3fa9 --- /dev/null +++ b/docs/src/components/components/mapviewstate/MapViewStateCameraFullExample.astro @@ -0,0 +1,27 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; + durationMillis?: number; +} + +const { + latitude = 35.6586, + longitude = 139.7454, + zoom = 15, + durationMillis = 500, +} = Astro.props; + +const code = `mapViewState.moveCameraTo( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} + ), + durationMillis: ${durationMillis} +)`; +--- + + diff --git a/docs/src/components/components/mapviewstate/MapViewStateCameraPointExample.astro b/docs/src/components/components/mapviewstate/MapViewStateCameraPointExample.astro new file mode 100644 index 0000000..d5811c1 --- /dev/null +++ b/docs/src/components/components/mapviewstate/MapViewStateCameraPointExample.astro @@ -0,0 +1,22 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + durationMillis?: number; +} + +const { + latitude = 35.7101, + longitude = 139.8107, + durationMillis = 500, +} = Astro.props; + +const code = `mapViewState.moveCameraTo( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + durationMillis: ${durationMillis} +)`; +--- + + diff --git a/docs/src/components/components/mapviewstate/MapViewStateExample.astro b/docs/src/components/components/mapviewstate/MapViewStateExample.astro new file mode 100644 index 0000000..e452cf2 --- /dev/null +++ b/docs/src/components/components/mapviewstate/MapViewStateExample.astro @@ -0,0 +1,24 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoom = 13, +} = Astro.props; + +const code = `@StateObject private var mapViewState = GoogleMapViewState( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} + ) +)`; +--- + + diff --git a/docs/src/components/components/mapviewstate/MapViewStateHolderExample.astro b/docs/src/components/components/mapviewstate/MapViewStateHolderExample.astro new file mode 100644 index 0000000..1659c60 --- /dev/null +++ b/docs/src/components/components/mapviewstate/MapViewStateHolderExample.astro @@ -0,0 +1,10 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `if let holder = mapViewState.getMapViewHolder(), + let nativeMapView = holder.mapView as? GMSMapView { + nativeMapView.isTrafficEnabled = true +}`; +--- + + diff --git a/docs/src/components/components/mapviewstate/MapViewStatePassExample.astro b/docs/src/components/components/mapviewstate/MapViewStatePassExample.astro new file mode 100644 index 0000000..2ef7a8a --- /dev/null +++ b/docs/src/components/components/mapviewstate/MapViewStatePassExample.astro @@ -0,0 +1,19 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `GoogleMapView(state: mapViewState) { + Marker(position: GeoPoint(latitude: ${latitude}, longitude: ${longitude})) +}`; +--- + + diff --git a/docs/src/components/components/marker/MarkerBasicExample.astro b/docs/src/components/components/marker/MarkerBasicExample.astro new file mode 100644 index 0000000..4011df3 --- /dev/null +++ b/docs/src/components/components/marker/MarkerBasicExample.astro @@ -0,0 +1,27 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; + latitude?: number; + longitude?: number; + label?: string; +} + +const { + commentForClick = "Clicked marker", + latitude = 35.6812, + longitude = 139.7671, + label = "T", +} = Astro.props; + +const code = `Marker( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + icon: DefaultMarkerIcon(label: "${label}"), + onClick: { state in + print("${commentForClick} \\(state.id)") + } +)`; +--- + + diff --git a/docs/src/components/components/marker/MarkerForArrayExample.astro b/docs/src/components/components/marker/MarkerForArrayExample.astro new file mode 100644 index 0000000..d4c29fb --- /dev/null +++ b/docs/src/components/components/marker/MarkerForArrayExample.astro @@ -0,0 +1,20 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `let markers: [MarkerState] = places.map { place in + MarkerState( + position: place.coordinate, + id: place.id, + extra: place.name, + icon: DefaultMarkerIcon(label: place.shortLabel) + ) +} + +GoogleMapView(state: mapViewState) { + ForArray(markers) { marker in + Marker(state: marker) + } +}`; +--- + + diff --git a/docs/src/components/components/marker/MarkerInitializer.astro b/docs/src/components/components/marker/MarkerInitializer.astro new file mode 100644 index 0000000..6816f4a --- /dev/null +++ b/docs/src/components/components/marker/MarkerInitializer.astro @@ -0,0 +1,21 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `Marker( + position: GeoPoint, + id: String? = nil, + extra: Any? = nil, + icon: (any MarkerIconProtocol)? = nil, + animation: MarkerAnimation? = nil, + clickable: Bool = true, + draggable: Bool = false, + onClick: OnMarkerEventHandler? = nil, + onDragStart: OnMarkerEventHandler? = nil, + onDrag: OnMarkerEventHandler? = nil, + onDragEnd: OnMarkerEventHandler? = nil, + onAnimateStart: OnMarkerEventHandler? = nil, + onAnimateEnd: OnMarkerEventHandler? = nil +)`; +--- + + diff --git a/docs/src/components/components/marker/MarkerInteractionExample.astro b/docs/src/components/components/marker/MarkerInteractionExample.astro new file mode 100644 index 0000000..a255004 --- /dev/null +++ b/docs/src/components/components/marker/MarkerInteractionExample.astro @@ -0,0 +1,32 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; + commentForDragEnd?: string; + latitude?: number; + longitude?: number; +} + +const { + commentForClick = "Clicked", + commentForDragEnd = "New position", + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `Marker( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + id: "tokyo-station", + extra: "Tokyo Station", + draggable: true, + onClick: { marker in + print("${commentForClick} \\(marker.extra ?? marker.id)") + }, + onDragEnd: { marker in + print("${commentForDragEnd}: \\(marker.position)") + } +)`; +--- + + diff --git a/docs/src/components/components/marker/MarkerStateExample.astro b/docs/src/components/components/marker/MarkerStateExample.astro new file mode 100644 index 0000000..0e0c165 --- /dev/null +++ b/docs/src/components/components/marker/MarkerStateExample.astro @@ -0,0 +1,30 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + updatedLatitude?: number; + updatedLongitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + updatedLatitude = 35.6895, + updatedLongitude = 139.6917, +} = Astro.props; + +const code = `let markerState = MarkerState( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + extra: "Tokyo Station", + icon: DefaultMarkerIcon(fillColor: .red, label: "T") +) + +Marker(state: markerState) + +markerState.position = GeoPoint(latitude: ${updatedLatitude}, longitude: ${updatedLongitude}) +markerState.animate(.Bounce)`; +--- + + diff --git a/docs/src/components/components/polygon/PolygonBasicExample.astro b/docs/src/components/components/polygon/PolygonBasicExample.astro new file mode 100644 index 0000000..6e5c060 --- /dev/null +++ b/docs/src/components/components/polygon/PolygonBasicExample.astro @@ -0,0 +1,29 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; + strokeWidth?: number; +} + +const { + commentForClick = "Clicked at", + strokeWidth = 2, +} = Astro.props; + +const code = `Polygon( + points: [ + GeoPoint(latitude: 35.67, longitude: 139.75), + GeoPoint(latitude: 35.68, longitude: 139.76), + GeoPoint(latitude: 35.66, longitude: 139.77) + ], + strokeColor: .blue, + strokeWidth: ${strokeWidth}, + fillColor: UIColor.blue.withAlphaComponent(0.2), + onClick: { event in + print("${commentForClick} \\(event.clicked)") + } +)`; +--- + + diff --git a/docs/src/components/components/polygon/PolygonClickExample.astro b/docs/src/components/components/polygon/PolygonClickExample.astro new file mode 100644 index 0000000..8225163 --- /dev/null +++ b/docs/src/components/components/polygon/PolygonClickExample.astro @@ -0,0 +1,20 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; +} + +const { + commentForClick = "Polygon clicked at", +} = Astro.props; + +const code = `Polygon( + points: districtBoundary, + onClick: { event in + print("${commentForClick} \\(event.clicked)") + } +)`; +--- + + diff --git a/docs/src/components/components/polygon/PolygonHolesExample.astro b/docs/src/components/components/polygon/PolygonHolesExample.astro new file mode 100644 index 0000000..da452c3 --- /dev/null +++ b/docs/src/components/components/polygon/PolygonHolesExample.astro @@ -0,0 +1,27 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `let outerRing = [ + GeoPoint(latitude: 35.67, longitude: 139.74), + GeoPoint(latitude: 35.69, longitude: 139.74), + GeoPoint(latitude: 35.69, longitude: 139.77), + GeoPoint(latitude: 35.67, longitude: 139.77), +] + +let innerRing = [ + GeoPoint(latitude: 35.675, longitude: 139.75), + GeoPoint(latitude: 35.685, longitude: 139.75), + GeoPoint(latitude: 35.685, longitude: 139.76), + GeoPoint(latitude: 35.675, longitude: 139.76), +] + +Polygon( + state: PolygonState( + points: outerRing, + fillColor: UIColor.green.withAlphaComponent(0.25), + holes: [innerRing] + ) +)`; +--- + + diff --git a/docs/src/components/components/polygon/PolygonInitializer.astro b/docs/src/components/components/polygon/PolygonInitializer.astro new file mode 100644 index 0000000..b464a0b --- /dev/null +++ b/docs/src/components/components/polygon/PolygonInitializer.astro @@ -0,0 +1,17 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `Polygon( + points: [GeoPointProtocol], + id: String? = nil, + strokeColor: UIColor = .black, + strokeWidth: Double = 1.0, + fillColor: UIColor = .clear, + geodesic: Bool = false, + zIndex: Int = 0, + extra: Any? = nil, + onClick: OnPolygonEventHandler? = nil +)`; +--- + + diff --git a/docs/src/components/components/polygon/PolygonStateWithHolesExample.astro b/docs/src/components/components/polygon/PolygonStateWithHolesExample.astro new file mode 100644 index 0000000..1f72c95 --- /dev/null +++ b/docs/src/components/components/polygon/PolygonStateWithHolesExample.astro @@ -0,0 +1,13 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `let state = PolygonState( + points: outerRing, + fillColor: UIColor.green.withAlphaComponent(0.25), + holes: [innerRing] +) + +Polygon(state: state)`; +--- + + diff --git a/docs/src/components/components/polyline/PolylineBasicExample.astro b/docs/src/components/components/polyline/PolylineBasicExample.astro new file mode 100644 index 0000000..9194ade --- /dev/null +++ b/docs/src/components/components/polyline/PolylineBasicExample.astro @@ -0,0 +1,30 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; + strokeWidth?: number; +} + +const { + commentForClick = "Clicked at", + strokeWidth = 4, +} = Astro.props; + +const code = `let route: [GeoPoint] = [ + GeoPoint(latitude: 35.6812, longitude: 139.7671), + GeoPoint(latitude: 35.6586, longitude: 139.7454) +] + +Polyline( + points: route, + strokeColor: .blue, + strokeWidth: ${strokeWidth}, + geodesic: false, + onClick: { event in + print("${commentForClick} \\(event.clicked)") + } +)`; +--- + + diff --git a/docs/src/components/components/polyline/PolylineBoundsExample.astro b/docs/src/components/components/polyline/PolylineBoundsExample.astro new file mode 100644 index 0000000..dc37c3f --- /dev/null +++ b/docs/src/components/components/polyline/PolylineBoundsExample.astro @@ -0,0 +1,20 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + strokeWidth?: number; +} + +const { + strokeWidth = 2, +} = Astro.props; + +const code = `let bounds = GeoRectBounds( + southWest: GeoPoint(latitude: 35.67, longitude: 139.74), + northEast: GeoPoint(latitude: 35.69, longitude: 139.76) +) + +Polyline(bounds: bounds, strokeColor: .red, strokeWidth: ${strokeWidth})`; +--- + + diff --git a/docs/src/components/components/polyline/PolylineClickExample.astro b/docs/src/components/components/polyline/PolylineClickExample.astro new file mode 100644 index 0000000..f678aee --- /dev/null +++ b/docs/src/components/components/polyline/PolylineClickExample.astro @@ -0,0 +1,24 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; + strokeWidth?: number; +} + +const { + commentForClick = "Polyline clicked at", + strokeWidth = 4, +} = Astro.props; + +const code = `Polyline( + points: route, + strokeColor: .blue, + strokeWidth: ${strokeWidth}, + onClick: { event in + print("${commentForClick} \\(event.clicked)") + } +)`; +--- + + diff --git a/docs/src/components/components/polyline/PolylineInitializerBounds.astro b/docs/src/components/components/polyline/PolylineInitializerBounds.astro new file mode 100644 index 0000000..19a46ee --- /dev/null +++ b/docs/src/components/components/polyline/PolylineInitializerBounds.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `Polyline( + bounds: GeoRectBounds, + id: String? = nil, + strokeColor: UIColor = .black, + strokeWidth: Double = 1.0, + geodesic: Bool = false, + extra: Any? = nil, + onClick: OnPolylineEventHandler? = nil +)`; +--- + + diff --git a/docs/src/components/components/polyline/PolylineInitializerPoints.astro b/docs/src/components/components/polyline/PolylineInitializerPoints.astro new file mode 100644 index 0000000..cd35ab7 --- /dev/null +++ b/docs/src/components/components/polyline/PolylineInitializerPoints.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `Polyline( + points: [GeoPointProtocol], + id: String? = nil, + strokeColor: UIColor = .black, + strokeWidth: Double = 1.0, + geodesic: Bool = false, + extra: Any? = nil, + onClick: OnPolylineEventHandler? = nil +)`; +--- + + diff --git a/docs/src/components/components/polyline/PolylineStateExample.astro b/docs/src/components/components/polyline/PolylineStateExample.astro new file mode 100644 index 0000000..6628e7b --- /dev/null +++ b/docs/src/components/components/polyline/PolylineStateExample.astro @@ -0,0 +1,12 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `let state = PolylineState(points: route, strokeColor: .blue, strokeWidth: 3) + +Polyline(state: state) + +state.points.append(GeoPoint(latitude: 35.7101, longitude: 139.8107)) +state.strokeColor = .red`; +--- + + diff --git a/docs/src/components/core/geopoint/GeoPointBasicExample.astro b/docs/src/components/core/geopoint/GeoPointBasicExample.astro new file mode 100644 index 0000000..61e410e --- /dev/null +++ b/docs/src/components/core/geopoint/GeoPointBasicExample.astro @@ -0,0 +1,17 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `let point = GeoPoint(latitude: ${latitude}, longitude: ${longitude})`; +--- + + diff --git a/docs/src/components/core/geopoint/GeoPointCoordinateOrderExample.astro b/docs/src/components/core/geopoint/GeoPointCoordinateOrderExample.astro new file mode 100644 index 0000000..ccf457d --- /dev/null +++ b/docs/src/components/core/geopoint/GeoPointCoordinateOrderExample.astro @@ -0,0 +1,17 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `let tokyo = GeoPoint(latitude: ${latitude}, longitude: ${longitude})`; +--- + + diff --git a/docs/src/components/core/geopoint/GeoPointFromLatLongExample.astro b/docs/src/components/core/geopoint/GeoPointFromLatLongExample.astro new file mode 100644 index 0000000..33bdee5 --- /dev/null +++ b/docs/src/components/core/geopoint/GeoPointFromLatLongExample.astro @@ -0,0 +1,18 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `let fromLatLong = GeoPoint.fromLatLong(latitude: ${latitude}, longitude: ${longitude}) +let fromLongLat = GeoPoint.fromLongLat(longitude: ${longitude}, latitude: ${latitude})`; +--- + + diff --git a/docs/src/components/core/geopoint/GeoPointNormalizeExample.astro b/docs/src/components/core/geopoint/GeoPointNormalizeExample.astro new file mode 100644 index 0000000..fb2185b --- /dev/null +++ b/docs/src/components/core/geopoint/GeoPointNormalizeExample.astro @@ -0,0 +1,20 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 95, + longitude = 190, +} = Astro.props; + +const code = `let raw = GeoPoint(latitude: ${latitude}, longitude: ${longitude}) +let normalized = raw.normalize() +let valid = normalized.isValid() +let urlValue = normalized.toUrlValue()`; +--- + + diff --git a/docs/src/components/core/geopoint/GeoPointSphericalExample.astro b/docs/src/components/core/geopoint/GeoPointSphericalExample.astro new file mode 100644 index 0000000..0547e6c --- /dev/null +++ b/docs/src/components/core/geopoint/GeoPointSphericalExample.astro @@ -0,0 +1,8 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `let distanceMeters = Spherical.computeDistanceBetween(from: pointA, to: pointB) +let headingDegrees = Spherical.computeHeading(from: pointA, to: pointB)`; +--- + + diff --git a/docs/src/components/core/geopoint/GeoPointUrlValueExample.astro b/docs/src/components/core/geopoint/GeoPointUrlValueExample.astro new file mode 100644 index 0000000..bd8e00b --- /dev/null +++ b/docs/src/components/core/geopoint/GeoPointUrlValueExample.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + precision?: number; +} + +const { + precision = 5, +} = Astro.props; + +const code = `let value = tokyo.toUrlValue(precision: ${precision})`; +--- + + diff --git a/docs/src/components/core/geopoint/GeoPointValidationExample.astro b/docs/src/components/core/geopoint/GeoPointValidationExample.astro new file mode 100644 index 0000000..5d0d8c9 --- /dev/null +++ b/docs/src/components/core/geopoint/GeoPointValidationExample.astro @@ -0,0 +1,23 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 181.5, +} = Astro.props; + +const code = `let input = GeoPoint(latitude: ${latitude}, longitude: ${longitude}) + +if input.isValid() { + Marker(position: input) +} else { + Marker(position: input.normalize()) +}`; +--- + + diff --git a/docs/src/components/core/georectbounds/GeoRectBoundsBasicExample.astro b/docs/src/components/core/georectbounds/GeoRectBoundsBasicExample.astro new file mode 100644 index 0000000..c4dbd45 --- /dev/null +++ b/docs/src/components/core/georectbounds/GeoRectBoundsBasicExample.astro @@ -0,0 +1,24 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + swLat?: number; + swLon?: number; + neLat?: number; + neLon?: number; +} + +const { + swLat = 35.67, + swLon = 139.74, + neLat = 35.69, + neLon = 139.76, +} = Astro.props; + +const code = `let bounds = GeoRectBounds( + southWest: GeoPoint(latitude: ${swLat}, longitude: ${swLon}), + northEast: GeoPoint(latitude: ${neLat}, longitude: ${neLon}) +)`; +--- + + diff --git a/docs/src/components/core/georectbounds/GeoRectBoundsContainmentExample.astro b/docs/src/components/core/georectbounds/GeoRectBoundsContainmentExample.astro new file mode 100644 index 0000000..c056d72 --- /dev/null +++ b/docs/src/components/core/georectbounds/GeoRectBoundsContainmentExample.astro @@ -0,0 +1,13 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `if bounds.contains(point: candidate) { + visiblePoints.append(candidate) +} + +if searchArea.intersects(other: loadedTileBounds) { + loadMoreData() +}`; +--- + + diff --git a/docs/src/components/core/georectbounds/GeoRectBoundsFitBoundsExample.astro b/docs/src/components/core/georectbounds/GeoRectBoundsFitBoundsExample.astro new file mode 100644 index 0000000..e778999 --- /dev/null +++ b/docs/src/components/core/georectbounds/GeoRectBoundsFitBoundsExample.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + padding?: number; +} + +const { + padding = 32, +} = Astro.props; + +const code = `mapViewState.fitBounds(bounds: bounds, padding: ${padding})`; +--- + + diff --git a/docs/src/components/core/georectbounds/GeoRectBoundsFromPointsExample.astro b/docs/src/components/core/georectbounds/GeoRectBoundsFromPointsExample.astro new file mode 100644 index 0000000..6ae25b9 --- /dev/null +++ b/docs/src/components/core/georectbounds/GeoRectBoundsFromPointsExample.astro @@ -0,0 +1,27 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + zoom?: number; + durationMillis?: number; +} + +const { + zoom = 12, + durationMillis = 300, +} = Astro.props; + +const code = `let bounds = GeoRectBounds() +for point in points { + bounds.extend(point: point) +} + +if let center = bounds.center { + mapViewState.moveCameraTo( + cameraPosition: MapCameraPosition(position: center, zoom: ${zoom}), + durationMillis: ${durationMillis} + ) +}`; +--- + + diff --git a/docs/src/components/core/georectbounds/GeoRectBoundsPolylineExample.astro b/docs/src/components/core/georectbounds/GeoRectBoundsPolylineExample.astro new file mode 100644 index 0000000..674a064 --- /dev/null +++ b/docs/src/components/core/georectbounds/GeoRectBoundsPolylineExample.astro @@ -0,0 +1,19 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + strokeWidth?: number; +} + +const { + strokeWidth = 2, +} = Astro.props; + +const code = `Polyline( + bounds: bounds, + strokeColor: .red, + strokeWidth: ${strokeWidth} +)`; +--- + + diff --git a/docs/src/components/core/mapcameraposition/CameraEventsExample.astro b/docs/src/components/core/mapcameraposition/CameraEventsExample.astro new file mode 100644 index 0000000..400c703 --- /dev/null +++ b/docs/src/components/core/mapcameraposition/CameraEventsExample.astro @@ -0,0 +1,32 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + startedAtComment?: string; + movingAtComment?: string; + endedAtComment?: string; +} + +const { + startedAtComment = "Started at \\(camera.position)", + movingAtComment = "Moving at zoom \\(camera.zoom)", + endedAtComment = "Ended at \\(camera.position)", +} = Astro.props; + +const code = `GoogleMapView( + state: mapViewState, + onCameraMoveStart: { camera in + print("${startedAtComment}") + }, + onCameraMove: { camera in + print("${movingAtComment}") + }, + onCameraMoveEnd: { camera in + print("${endedAtComment}") + } +) { + Marker(position: mapViewState.cameraPosition.position) +}`; +--- + + diff --git a/docs/src/components/core/mapcameraposition/CopyCameraExample.astro b/docs/src/components/core/mapcameraposition/CopyCameraExample.astro new file mode 100644 index 0000000..de5efa8 --- /dev/null +++ b/docs/src/components/core/mapcameraposition/CopyCameraExample.astro @@ -0,0 +1,18 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + zoom?: number; + durationMillis?: number; +} + +const { + zoom = 16, + durationMillis = 300, +} = Astro.props; + +const code = `let next = mapViewState.cameraPosition.copy(zoom: ${zoom}) +mapViewState.moveCameraTo(cameraPosition: next, durationMillis: ${durationMillis})`; +--- + + diff --git a/docs/src/components/core/mapcameraposition/FitBoundsExample.astro b/docs/src/components/core/mapcameraposition/FitBoundsExample.astro new file mode 100644 index 0000000..e129061 --- /dev/null +++ b/docs/src/components/core/mapcameraposition/FitBoundsExample.astro @@ -0,0 +1,27 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + swLat?: number; + swLon?: number; + neLat?: number; + neLon?: number; + padding?: number; +} + +const { + swLat = 35.67, + swLon = 139.74, + neLat = 35.69, + neLon = 139.76, + padding = 32, +} = Astro.props; + +const code = `let bounds = GeoRectBounds( + southWest: GeoPoint(latitude: ${swLat}, longitude: ${swLon}), + northEast: GeoPoint(latitude: ${neLat}, longitude: ${neLon}) +) +mapViewState.fitBounds(bounds: bounds, padding: ${padding})`; +--- + + diff --git a/docs/src/components/core/mapcameraposition/MapCameraPositionBasicExample.astro b/docs/src/components/core/mapcameraposition/MapCameraPositionBasicExample.astro new file mode 100644 index 0000000..c9337bf --- /dev/null +++ b/docs/src/components/core/mapcameraposition/MapCameraPositionBasicExample.astro @@ -0,0 +1,28 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; + bearing?: number; + tilt?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoom = 13, + bearing = 0, + tilt = 0, +} = Astro.props; + +const code = `let camera = MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom}, + bearing: ${bearing}, + tilt: ${tilt} +)`; +--- + + diff --git a/docs/src/components/core/mapcameraposition/MapCameraPositionInitializerExample.astro b/docs/src/components/core/mapcameraposition/MapCameraPositionInitializerExample.astro new file mode 100644 index 0000000..bb4ef79 --- /dev/null +++ b/docs/src/components/core/mapcameraposition/MapCameraPositionInitializerExample.astro @@ -0,0 +1,14 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `MapCameraPosition( + position: GeoPointProtocol, + zoom: Double = 0.0, + bearing: Double = 0.0, + tilt: Double = 0.0, + paddings: MapPaddingsProtocol? = MapPaddings.Zeros, + visibleRegion: VisibleRegion? = nil +)`; +--- + + diff --git a/docs/src/components/core/mapcameraposition/MoveCameraExample.astro b/docs/src/components/core/mapcameraposition/MoveCameraExample.astro new file mode 100644 index 0000000..1a77951 --- /dev/null +++ b/docs/src/components/core/mapcameraposition/MoveCameraExample.astro @@ -0,0 +1,38 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + lat1?: number; + lon1?: number; + zoom1?: number; + duration1?: number; + lat2?: number; + lon2?: number; + duration2?: number; +} + +const { + lat1 = 35.6586, + lon1 = 139.7454, + zoom1 = 15, + duration1 = 500, + lat2 = 35.7101, + lon2 = 139.8107, + duration2 = 500, +} = Astro.props; + +const code = `mapViewState.moveCameraTo( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: ${lat1}, longitude: ${lon1}), + zoom: ${zoom1} + ), + durationMillis: ${duration1} +) + +mapViewState.moveCameraTo( + position: GeoPoint(latitude: ${lat2}, longitude: ${lon2}), + durationMillis: ${duration2} +)`; +--- + + diff --git a/docs/src/components/core/marker-icons/BitmapIconExample.astro b/docs/src/components/core/marker-icons/BitmapIconExample.astro new file mode 100644 index 0000000..b7c9b2f --- /dev/null +++ b/docs/src/components/core/marker-icons/BitmapIconExample.astro @@ -0,0 +1,23 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + anchorX?: number; + anchorY?: number; + scale?: number; +} + +const { + anchorX = 0.5, + anchorY = 1.0, + scale = 1.0, +} = Astro.props; + +const code = `let icon = BitmapIcon( + bitmap: image, + anchor: CGPoint(x: ${anchorX}, y: ${anchorY}), + scale: ${scale} +)`; +--- + + diff --git a/docs/src/components/core/marker-icons/DefaultMarkerIconExample.astro b/docs/src/components/core/marker-icons/DefaultMarkerIconExample.astro new file mode 100644 index 0000000..6ed10a0 --- /dev/null +++ b/docs/src/components/core/marker-icons/DefaultMarkerIconExample.astro @@ -0,0 +1,28 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + label?: string; + latitude?: number; + longitude?: number; +} + +const { + label = "A", + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `let icon = DefaultMarkerIcon( + fillColor: .red, + strokeColor: .white, + label: "${label}" +) + +Marker( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + icon: icon +)`; +--- + + diff --git a/docs/src/components/core/marker-icons/ImageDefaultIconExample.astro b/docs/src/components/core/marker-icons/ImageDefaultIconExample.astro new file mode 100644 index 0000000..798a20e --- /dev/null +++ b/docs/src/components/core/marker-icons/ImageDefaultIconExample.astro @@ -0,0 +1,21 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + imageName?: string; + label?: string; +} + +const { + imageName = "avatar", + label = "1", +} = Astro.props; + +const code = `let icon = ImageDefaultIcon( + backgroundImage: UIImage(named: "${imageName}")!, + strokeColor: .white, + label: "${label}" +)`; +--- + + diff --git a/docs/src/components/core/marker-icons/ImageIconExample.astro b/docs/src/components/core/marker-icons/ImageIconExample.astro new file mode 100644 index 0000000..983588f --- /dev/null +++ b/docs/src/components/core/marker-icons/ImageIconExample.astro @@ -0,0 +1,25 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + imageName?: string; + iconSize?: number; + anchorX?: number; + anchorY?: number; +} + +const { + imageName = "custom-marker", + iconSize = 48, + anchorX = 0.5, + anchorY = 1.0, +} = Astro.props; + +const code = `let icon = ImageIcon( + image: UIImage(named: "${imageName}")!, + iconSize: ${iconSize}, + anchor: CGPoint(x: ${anchorX}, y: ${anchorY}) +)`; +--- + + diff --git a/docs/src/components/core/spherical-utilities/SphericalAreaExample.astro b/docs/src/components/core/spherical-utilities/SphericalAreaExample.astro new file mode 100644 index 0000000..ce6b439 --- /dev/null +++ b/docs/src/components/core/spherical-utilities/SphericalAreaExample.astro @@ -0,0 +1,8 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `let areaSquareMeters = Spherical.computeArea(polygonPoints) +let signedArea = Spherical.computeSignedArea(polygonPoints)`; +--- + + diff --git a/docs/src/components/core/spherical-utilities/SphericalBasicExample.astro b/docs/src/components/core/spherical-utilities/SphericalBasicExample.astro new file mode 100644 index 0000000..c751e9d --- /dev/null +++ b/docs/src/components/core/spherical-utilities/SphericalBasicExample.astro @@ -0,0 +1,30 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + tokyoLat?: number; + tokyoLon?: number; + towerLat?: number; + towerLon?: number; + offsetDistance?: number; + offsetHeading?: number; +} + +const { + tokyoLat = 35.6812, + tokyoLon = 139.7671, + towerLat = 35.6586, + towerLon = 139.7454, + offsetDistance = 1_000, + offsetHeading = 90, +} = Astro.props; + +const code = `let tokyo = GeoPoint(latitude: ${tokyoLat}, longitude: ${tokyoLon}) +let tower = GeoPoint(latitude: ${towerLat}, longitude: ${towerLon}) + +let distance = Spherical.computeDistanceBetween(from: tokyo, to: tower) +let heading = Spherical.computeHeading(from: tokyo, to: tower) +let point = Spherical.computeOffset(origin: tokyo, distance: ${offsetDistance}, heading: ${offsetHeading})`; +--- + + diff --git a/docs/src/components/core/spherical-utilities/SphericalInterpolateExample.astro b/docs/src/components/core/spherical-utilities/SphericalInterpolateExample.astro new file mode 100644 index 0000000..1252768 --- /dev/null +++ b/docs/src/components/core/spherical-utilities/SphericalInterpolateExample.astro @@ -0,0 +1,19 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + fraction?: number; +} + +const { + fraction = 0.5, +} = Astro.props; + +const code = `let halfway = Spherical.interpolate( + from: pointA, + to: pointB, + fraction: ${fraction} +)`; +--- + + diff --git a/docs/src/components/core/spherical-utilities/SphericalLengthAreaExample.astro b/docs/src/components/core/spherical-utilities/SphericalLengthAreaExample.astro new file mode 100644 index 0000000..ce68d44 --- /dev/null +++ b/docs/src/components/core/spherical-utilities/SphericalLengthAreaExample.astro @@ -0,0 +1,8 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `let routeLength = Spherical.computeLength(route) +let polygonArea = Spherical.computeArea(polygon)`; +--- + + diff --git a/docs/src/components/core/spherical-utilities/SphericalRouteLengthExample.astro b/docs/src/components/core/spherical-utilities/SphericalRouteLengthExample.astro new file mode 100644 index 0000000..744aa7c --- /dev/null +++ b/docs/src/components/core/spherical-utilities/SphericalRouteLengthExample.astro @@ -0,0 +1,7 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `let routeLengthMeters = Spherical.computeLength(routePoints)`; +--- + + diff --git a/docs/src/components/core/zoom-levels/ZoomControlsExample.astro b/docs/src/components/core/zoom-levels/ZoomControlsExample.astro new file mode 100644 index 0000000..24a94e8 --- /dev/null +++ b/docs/src/components/core/zoom-levels/ZoomControlsExample.astro @@ -0,0 +1,38 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + minZoom?: number; + maxZoom?: number; + durationMillis?: number; +} + +const { + minZoom = 3.0, + maxZoom = 18.0, + durationMillis = 200, +} = Astro.props; + +const code = `let minZoom = ${minZoom} +let maxZoom = ${maxZoom} + +func zoomIn() { + let current = mapViewState.cameraPosition + let nextZoom = min(maxZoom, current.zoom + 1) + mapViewState.moveCameraTo( + cameraPosition: current.copy(zoom: nextZoom), + durationMillis: ${durationMillis} + ) +} + +func zoomOut() { + let current = mapViewState.cameraPosition + let nextZoom = max(minZoom, current.zoom - 1) + mapViewState.moveCameraTo( + cameraPosition: current.copy(zoom: nextZoom), + durationMillis: ${durationMillis} + ) +}`; +--- + + diff --git a/docs/src/components/core/zoom-levels/ZoomLevelBasicExample.astro b/docs/src/components/core/zoom-levels/ZoomLevelBasicExample.astro new file mode 100644 index 0000000..2db43aa --- /dev/null +++ b/docs/src/components/core/zoom-levels/ZoomLevelBasicExample.astro @@ -0,0 +1,26 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; + durationMillis?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoom = 14, + durationMillis = 300, +} = Astro.props; + +const code = `let camera = MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} +) + +mapViewState.moveCameraTo(cameraPosition: camera, durationMillis: ${durationMillis})`; +--- + + diff --git a/docs/src/components/event/EventHandlersMapExample.astro b/docs/src/components/event/EventHandlersMapExample.astro new file mode 100644 index 0000000..6f01681 --- /dev/null +++ b/docs/src/components/event/EventHandlersMapExample.astro @@ -0,0 +1,41 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `GoogleMapView( + state: mapViewState, + onMapLoaded: { state in + print("Loaded: \\(state.id)") + }, + onMapClick: { point in + print("Clicked: \\(point.latitude), \\(point.longitude)") + }, + onCameraMoveStart: { camera in + print("Move started: \\(camera.zoom)") + }, + onCameraMove: { camera in + print("Moving: \\(camera.position)") + }, + onCameraMoveEnd: { camera in + print("Move ended: \\(camera.zoom)") + } +) { + Marker( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + onClick: { marker in + print("Marker clicked: \\(marker.id)") + } + ) +}`; +--- + + diff --git a/docs/src/components/event/EventHandlersMarkerExample.astro b/docs/src/components/event/EventHandlersMarkerExample.astro new file mode 100644 index 0000000..5b0a9ba --- /dev/null +++ b/docs/src/components/event/EventHandlersMarkerExample.astro @@ -0,0 +1,26 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `Marker( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + draggable: true, + onClick: { state in + print("Clicked marker \\(state.id)") + }, + onDragEnd: { state in + print("Dropped at \\(state.position)") + } +)`; +--- + + diff --git a/docs/src/components/event/EventHandlersShapeExample.astro b/docs/src/components/event/EventHandlersShapeExample.astro new file mode 100644 index 0000000..9da4057 --- /dev/null +++ b/docs/src/components/event/EventHandlersShapeExample.astro @@ -0,0 +1,39 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + circleRadiusMeters?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + circleRadiusMeters = 500, +} = Astro.props; + +const code = `Circle( + center: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + radiusMeters: ${circleRadiusMeters}, + onClick: { event in + print("Circle clicked at \\(event.clicked)") + } +) + +Polyline( + points: route, + onClick: { event in + print("Polyline clicked at \\(event.clicked)") + } +) + +Polygon( + points: polygon, + onClick: { event in + print("Polygon clicked at \\(event.clicked)") + } +)`; +--- + + diff --git a/docs/src/components/event/OnMapLoadedCallbackExample.astro b/docs/src/components/event/OnMapLoadedCallbackExample.astro new file mode 100644 index 0000000..0cf37fe --- /dev/null +++ b/docs/src/components/event/OnMapLoadedCallbackExample.astro @@ -0,0 +1,24 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `GoogleMapView( + state: mapViewState, + onMapLoaded: { state in + print("Map loaded: \\(state.id)") + } +) { + Marker(position: GeoPoint(latitude: ${latitude}, longitude: ${longitude})) +}`; +--- + + diff --git a/docs/src/components/event/OnMapLoadedGoogleMapsInitExample.astro b/docs/src/components/event/OnMapLoadedGoogleMapsInitExample.astro new file mode 100644 index 0000000..77ad717 --- /dev/null +++ b/docs/src/components/event/OnMapLoadedGoogleMapsInitExample.astro @@ -0,0 +1,28 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + apiKeyPlaceholder?: string; +} + +const { + apiKeyPlaceholder = "YOUR_GOOGLE_MAPS_API_KEY", +} = Astro.props; + +const code = `import GoogleMaps + +@main +struct MyApp: App { + init() { + GMSServices.provideAPIKey("${apiKeyPlaceholder}") + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +}`; +--- + + diff --git a/docs/src/components/event/OnMapLoadedMapLibreExample.astro b/docs/src/components/event/OnMapLoadedMapLibreExample.astro new file mode 100644 index 0000000..bd874f6 --- /dev/null +++ b/docs/src/components/event/OnMapLoadedMapLibreExample.astro @@ -0,0 +1,25 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoom = 13, +} = Astro.props; + +const code = `let state = MapLibreViewState( + mapDesignType: MapLibreDesign.OsmBright, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} + ) +)`; +--- + + diff --git a/docs/src/components/event/OnMapLoadedMapboxInitExample.astro b/docs/src/components/event/OnMapLoadedMapboxInitExample.astro new file mode 100644 index 0000000..b464c6b --- /dev/null +++ b/docs/src/components/event/OnMapLoadedMapboxInitExample.astro @@ -0,0 +1,28 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + tokenPlaceholder?: string; +} + +const { + tokenPlaceholder = "YOUR_MAPBOX_PUBLIC_TOKEN", +} = Astro.props; + +const code = `import MapConductorForMapbox + +@main +struct MyApp: App { + init() { + initializeMapbox(accessToken: "${tokenPlaceholder}") + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +}`; +--- + + diff --git a/docs/src/components/event/OnMapLoadedPackagesExample.astro b/docs/src/components/event/OnMapLoadedPackagesExample.astro new file mode 100644 index 0000000..e0487df --- /dev/null +++ b/docs/src/components/event/OnMapLoadedPackagesExample.astro @@ -0,0 +1,31 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + coreVersion?: string; + googlemapsVersion?: string; + appName?: string; +} + +const { + coreVersion = "{CORE_MODULE_VERSION}", + googlemapsVersion = "{GOOGLEMAPS_MODULE_VERSION}", + appName = "YourApp", +} = Astro.props; + +const code = `dependencies: [ + .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "${coreVersion}"), + .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "${googlemapsVersion}"), +], +targets: [ + .target( + name: "${appName}", + dependencies: [ + .product(name: "MapConductorCore", package: "ios-sdk-core"), + .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), + ] + ), +]`; +--- + + diff --git a/docs/src/components/experimental/geojson-layer/GeoJSONBasicExample.astro b/docs/src/components/experimental/geojson-layer/GeoJSONBasicExample.astro new file mode 100644 index 0000000..7f0aaf6 --- /dev/null +++ b/docs/src/components/experimental/geojson-layer/GeoJSONBasicExample.astro @@ -0,0 +1,38 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + strokeAlpha?: number; + fillAlpha?: number; + strokeWidth?: number; + pointRadius?: number; +} + +const { + strokeAlpha = 0.8, + fillAlpha = 0.15, + strokeWidth = 3, + pointRadius = 8, +} = Astro.props; + +const code = `import MapConductorCore +import MapConductorGeoJSON + +let style = GeoJSONTileRenderer.LayerStyle( + strokeColor: UIColor.red.withAlphaComponent(${strokeAlpha}), + fillColor: UIColor.red.withAlphaComponent(${fillAlpha}), + strokeWidth: ${strokeWidth}, + pointRadius: ${pointRadius} +) + +@StateObject private var layerState = GeoJSONLayerState(layerStyle: style) +@State private var features: [GeoJSONFeature] = [] + +var body: some View { + MapKitMapView(state: mapViewState) { + GeoJSONLayer(layerState, features: features) + } +}`; +--- + + diff --git a/docs/src/components/experimental/geojson-layer/GeoJSONClickHandler.astro b/docs/src/components/experimental/geojson-layer/GeoJSONClickHandler.astro new file mode 100644 index 0000000..f18c7f4 --- /dev/null +++ b/docs/src/components/experimental/geojson-layer/GeoJSONClickHandler.astro @@ -0,0 +1,17 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + printComment?: string; +} + +const { + printComment = "Tapped feature \\(feature.id ?? \"?\") at \\(geoPoint)", +} = Astro.props; + +const code = `layerState.onClick = { feature, geoPoint in + print("${printComment}") +}`; +--- + + diff --git a/docs/src/components/experimental/geojson-layer/GeoJSONClickHandlerMap.astro b/docs/src/components/experimental/geojson-layer/GeoJSONClickHandlerMap.astro new file mode 100644 index 0000000..23da2d5 --- /dev/null +++ b/docs/src/components/experimental/geojson-layer/GeoJSONClickHandlerMap.astro @@ -0,0 +1,14 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `MapKitMapView( + state: mapViewState, + onMapClick: { point in + layerState.processClick(geoPoint: point) + } +) { + GeoJSONLayer(layerState, features: features) +}`; +--- + + diff --git a/docs/src/components/experimental/geojson-layer/GeoJSONFeatureState.astro b/docs/src/components/experimental/geojson-layer/GeoJSONFeatureState.astro new file mode 100644 index 0000000..d477987 --- /dev/null +++ b/docs/src/components/experimental/geojson-layer/GeoJSONFeatureState.astro @@ -0,0 +1,19 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentToggle?: string; +} + +const { + commentToggle = "Toggle a feature's visibility", +} = Astro.props; + +const code = `let featureStates = features.map { GeoJSONFeatureState(feature: $0) } +GeoJSONLayer(state: layerState, features: featureStates.map { $0.toFeature() }) + +// ${commentToggle} +featureStates[0].visible = false`; +--- + + diff --git a/docs/src/components/experimental/geojson-layer/GeoJSONLayerStateInit.astro b/docs/src/components/experimental/geojson-layer/GeoJSONLayerStateInit.astro new file mode 100644 index 0000000..39782ba --- /dev/null +++ b/docs/src/components/experimental/geojson-layer/GeoJSONLayerStateInit.astro @@ -0,0 +1,13 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `GeoJSONLayerState( + tileSize: Int = 256, + opacity: Double = 1.0, + minZoom: Int = 0, + maxZoom: Int = 18, + layerStyle: GeoJSONTileRenderer.LayerStyle = .init() +)`; +--- + + diff --git a/docs/src/components/experimental/geojson-layer/GeoJSONLayerStateUpdate.astro b/docs/src/components/experimental/geojson-layer/GeoJSONLayerStateUpdate.astro new file mode 100644 index 0000000..4ec117a --- /dev/null +++ b/docs/src/components/experimental/geojson-layer/GeoJSONLayerStateUpdate.astro @@ -0,0 +1,27 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + opacity?: number; + strokeWidth?: number; + fillAlpha?: number; + pointRadius?: number; +} + +const { + opacity = 0.7, + strokeWidth = 2, + fillAlpha = 0.2, + pointRadius = 6, +} = Astro.props; + +const code = `layerState.opacity = ${opacity} +layerState.layerStyle = GeoJSONTileRenderer.LayerStyle( + strokeColor: .blue, + fillColor: UIColor.blue.withAlphaComponent(${fillAlpha}), + strokeWidth: ${strokeWidth}, + pointRadius: ${pointRadius} +)`; +--- + + diff --git a/docs/src/components/experimental/geojson-layer/GeoJSONLayerStyle.astro b/docs/src/components/experimental/geojson-layer/GeoJSONLayerStyle.astro new file mode 100644 index 0000000..2b85a20 --- /dev/null +++ b/docs/src/components/experimental/geojson-layer/GeoJSONLayerStyle.astro @@ -0,0 +1,12 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `GeoJSONTileRenderer.LayerStyle( + strokeColor: UIColor = .black, + fillColor: UIColor = .clear, + strokeWidth: CGFloat = 1, + pointRadius: CGFloat = 5 +)`; +--- + + diff --git a/docs/src/components/experimental/geojson-layer/GeoJSONLoadFeatures.astro b/docs/src/components/experimental/geojson-layer/GeoJSONLoadFeatures.astro new file mode 100644 index 0000000..d53cd3b --- /dev/null +++ b/docs/src/components/experimental/geojson-layer/GeoJSONLoadFeatures.astro @@ -0,0 +1,17 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentLoad?: string; +} + +const { + commentLoad = "Load from a URL", +} = Astro.props; + +const code = `// ${commentLoad} +let data = try Data(contentsOf: url) +let features = try GeoJSONParser.parse(data: data)`; +--- + + diff --git a/docs/src/components/experimental/geojson-layer/GeoJSONPackageInstall.astro b/docs/src/components/experimental/geojson-layer/GeoJSONPackageInstall.astro new file mode 100644 index 0000000..ce7edf4 --- /dev/null +++ b/docs/src/components/experimental/geojson-layer/GeoJSONPackageInstall.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + version?: string; +} + +const { + version = "{GEOJSON_MODULE_VERSION}", +} = Astro.props; + +const code = `.package(url: "https://github.com/MapConductor/ios-geojson-layer", from: "${version}")`; +--- + + diff --git a/docs/src/components/experimental/geojson-layer/GeoJSONProductDep.astro b/docs/src/components/experimental/geojson-layer/GeoJSONProductDep.astro new file mode 100644 index 0000000..d98f581 --- /dev/null +++ b/docs/src/components/experimental/geojson-layer/GeoJSONProductDep.astro @@ -0,0 +1,7 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `.product(name: "MapConductorGeoJSON", package: "ios-geojson-layer")`; +--- + + diff --git a/docs/src/components/experimental/heatmap/HeatmapBasicExample.astro b/docs/src/components/experimental/heatmap/HeatmapBasicExample.astro new file mode 100644 index 0000000..c56b5b1 --- /dev/null +++ b/docs/src/components/experimental/heatmap/HeatmapBasicExample.astro @@ -0,0 +1,45 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + radiusPx?: number; + opacity?: number; + lat1?: number; + lng1?: number; + weight1?: number; + lat2?: number; + lng2?: number; + weight2?: number; +} + +const { + radiusPx = 20, + opacity = 0.7, + lat1 = 35.6762, + lng1 = 139.6503, + weight1 = 1.0, + lat2 = 35.6895, + lng2 = 139.6917, + weight2 = 2.5, +} = Astro.props; + +const code = `import MapConductorHeatmap + +GoogleMapView(state: mapViewState) { + HeatmapOverlay( + radiusPx: ${radiusPx}, + opacity: ${opacity} + ) { + HeatmapPointView( + position: GeoPoint(latitude: ${lat1}, longitude: ${lng1}), + weight: ${weight1} + ) + HeatmapPointView( + position: GeoPoint(latitude: ${lat2}, longitude: ${lng2}), + weight: ${weight2} + ) + } +}`; +--- + + diff --git a/docs/src/components/experimental/heatmap/HeatmapBatchPoints.astro b/docs/src/components/experimental/heatmap/HeatmapBatchPoints.astro new file mode 100644 index 0000000..4e3e8f3 --- /dev/null +++ b/docs/src/components/experimental/heatmap/HeatmapBatchPoints.astro @@ -0,0 +1,21 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + radiusPx?: number; +} + +const { + radiusPx = 20, +} = Astro.props; + +const code = `let pointStates: [HeatmapPointState] = [...] + +GoogleMapView(state: mapViewState) { + HeatmapOverlay(radiusPx: ${radiusPx}) { + HeatmapPoints(pointStates) + } +}`; +--- + + diff --git a/docs/src/components/experimental/heatmap/HeatmapCustomGradient.astro b/docs/src/components/experimental/heatmap/HeatmapCustomGradient.astro new file mode 100644 index 0000000..74b5dc8 --- /dev/null +++ b/docs/src/components/experimental/heatmap/HeatmapCustomGradient.astro @@ -0,0 +1,27 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentBlue?: string; + commentGreen?: string; + commentRed?: string; +} + +const { + commentBlue = "Blue", + commentGreen = "Green", + commentRed = "Red", +} = Astro.props; + +const code = `let gradient = HeatmapGradient( + stops: [ + HeatmapGradientStop(position: 0.0, color: UIColor(red: 0, green: 0, blue: 1, alpha: 1)), // ${commentBlue} + HeatmapGradientStop(position: 0.5, color: UIColor(red: 0, green: 1, blue: 0, alpha: 1)), // ${commentGreen} + HeatmapGradientStop(position: 1.0, color: UIColor(red: 1, green: 0, blue: 0, alpha: 1)) // ${commentRed} + ] +) + +let heatmapState = HeatmapOverlayState(gradient: gradient)`; +--- + + diff --git a/docs/src/components/experimental/heatmap/HeatmapDynamicState.astro b/docs/src/components/experimental/heatmap/HeatmapDynamicState.astro new file mode 100644 index 0000000..bb96080 --- /dev/null +++ b/docs/src/components/experimental/heatmap/HeatmapDynamicState.astro @@ -0,0 +1,53 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + radiusPx?: number; + opacity?: number; + radiusMin?: number; + radiusMax?: number; + labelRadius?: string; + labelOpacity?: string; +} + +const { + radiusPx = 20, + opacity = 0.7, + radiusMin = 10, + radiusMax = 50, + labelRadius = "Radius:", + labelOpacity = "Opacity:", +} = Astro.props; + +const code = `struct DynamicHeatmapMap: View { + @StateObject private var heatmapState = HeatmapOverlayState( + radiusPx: ${radiusPx}, + opacity: ${opacity} + ) + @StateObject private var mapViewState = GoogleMapViewState() + + var body: some View { + VStack { + GoogleMapView(state: mapViewState) { + HeatmapOverlay(state: heatmapState) { + HeatmapPoints(pointStates) + } + } + + HStack { + Text("${labelRadius}") + Slider(value: .init( + get: { Double(heatmapState.radiusPx) }, + set: { heatmapState.radiusPx = Int($0) } + ), in: ${radiusMin}...${radiusMax}) + + Text("${labelOpacity}") + Slider(value: $heatmapState.opacity, in: 0...1) + } + .padding() + } + } +}`; +--- + + diff --git a/docs/src/components/experimental/heatmap/HeatmapPackageInstall.astro b/docs/src/components/experimental/heatmap/HeatmapPackageInstall.astro new file mode 100644 index 0000000..d723746 --- /dev/null +++ b/docs/src/components/experimental/heatmap/HeatmapPackageInstall.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + version?: string; +} + +const { + version = "{HEATMAP_MODULE_VERSION}", +} = Astro.props; + +const code = `.package(url: "https://github.com/MapConductor/ios-heatmap", from: "${version}"),`; +--- + + diff --git a/docs/src/components/experimental/heatmap/HeatmapProductDep.astro b/docs/src/components/experimental/heatmap/HeatmapProductDep.astro new file mode 100644 index 0000000..44cb7e4 --- /dev/null +++ b/docs/src/components/experimental/heatmap/HeatmapProductDep.astro @@ -0,0 +1,7 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `.product(name: "MapConductorHeatmap", package: "ios-heatmap"),`; +--- + + diff --git a/docs/src/components/experimental/marker-clustering/MarkerClusterBasicExample.astro b/docs/src/components/experimental/marker-clustering/MarkerClusterBasicExample.astro new file mode 100644 index 0000000..5818428 --- /dev/null +++ b/docs/src/components/experimental/marker-clustering/MarkerClusterBasicExample.astro @@ -0,0 +1,43 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + clusterRadiusPx?: number; + minClusterSize?: number; +} + +const { + clusterRadiusPx = 60, + minClusterSize = 2, +} = Astro.props; + +const code = `import MapConductorCore +import MapConductorForGoogleMaps +import MapConductorMarkerCluster + +struct ClusterMap: View { + @StateObject private var mapViewState = GoogleMapViewState() + + @State private var clusterStrategy = + MarkerClusterStrategy( + clusterRadiusPx: ${clusterRadiusPx}, + minClusterSize: ${minClusterSize}, + enableZoomAnimation: true, + enablePanAnimation: true + ) + + let markers: [MarkerState] + + var body: some View { + GoogleMapView(state: mapViewState) { + MarkerClusterGroup(strategy: clusterStrategy) { + ForArray(markers) { marker in + Marker(state: marker) + } + } + } + } +}`; +--- + + diff --git a/docs/src/components/experimental/marker-clustering/MarkerClusterOnClick.astro b/docs/src/components/experimental/marker-clustering/MarkerClusterOnClick.astro new file mode 100644 index 0000000..68f62a7 --- /dev/null +++ b/docs/src/components/experimental/marker-clustering/MarkerClusterOnClick.astro @@ -0,0 +1,23 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + printComment?: string; +} + +const { + printComment = "Cluster clicked: \\(cluster.count)", +} = Astro.props; + +const code = `MarkerClusterGroup( + onClusterClick: { cluster in + print("${printComment}") + } +) { + ForArray(markers) { marker in + Marker(state: marker) + } +}`; +--- + + diff --git a/docs/src/components/experimental/marker-clustering/MarkerClusterPackageInstall.astro b/docs/src/components/experimental/marker-clustering/MarkerClusterPackageInstall.astro new file mode 100644 index 0000000..037a841 --- /dev/null +++ b/docs/src/components/experimental/marker-clustering/MarkerClusterPackageInstall.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + version?: string; +} + +const { + version = "{MARKER_CLUSTERING_MODULE_VERSION}", +} = Astro.props; + +const code = `.package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "${version}")`; +--- + + diff --git a/docs/src/components/experimental/marker-clustering/MarkerClusterProductDep.astro b/docs/src/components/experimental/marker-clustering/MarkerClusterProductDep.astro new file mode 100644 index 0000000..b82cca6 --- /dev/null +++ b/docs/src/components/experimental/marker-clustering/MarkerClusterProductDep.astro @@ -0,0 +1,7 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `.product(name: "MapConductorMarkerCluster", package: "ios-marker-cluster")`; +--- + + diff --git a/docs/src/components/experimental/marker-clustering/MarkerClusterState.astro b/docs/src/components/experimental/marker-clustering/MarkerClusterState.astro new file mode 100644 index 0000000..4dedaac --- /dev/null +++ b/docs/src/components/experimental/marker-clustering/MarkerClusterState.astro @@ -0,0 +1,27 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + clusterRadiusPx?: number; + minClusterSize?: number; +} + +const { + clusterRadiusPx = 60, + minClusterSize = 2, +} = Astro.props; + +const code = `@StateObject private var clusterState = + MarkerClusterGroupState( + clusterRadiusPx: ${clusterRadiusPx}, + minClusterSize: ${minClusterSize} + ) + +MarkerClusterGroup(state: clusterState) { + ForArray(markers) { marker in + Marker(state: marker) + } +}`; +--- + + diff --git a/docs/src/components/get-started/GetStartedDisplayMapExample.astro b/docs/src/components/get-started/GetStartedDisplayMapExample.astro new file mode 100644 index 0000000..d2cb5e8 --- /dev/null +++ b/docs/src/components/get-started/GetStartedDisplayMapExample.astro @@ -0,0 +1,63 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; + polylineLatitude?: number; + polylineLongitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoom = 13, + polylineLatitude = 35.6586, + polylineLongitude = 139.7454, +} = Astro.props; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForMapKit + +struct ContentView: View { + @StateObject private var mapViewState = MapKitViewState( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} + ) + ) + + var body: some View { + MapKitMapView( + state: mapViewState, + onMapClick: { point in + print("Map clicked at \\(point)") + } + ) { + Marker( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + icon: DefaultMarkerIcon(fillColor: .red, label: "T"), + onClick: { marker in + print("Marker clicked: \\(marker.id)") + } + ) + + Circle( + center: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + radiusMeters: 1_000, + fillColor: UIColor.blue.withAlphaComponent(0.2) + ) + + Polyline(points: [ + GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + GeoPoint(latitude: ${polylineLatitude}, longitude: ${polylineLongitude}) + ]) + } + .ignoresSafeArea() + } +}`; +--- + + diff --git a/docs/src/components/get-started/GetStartedHandleInteractionsExample.astro b/docs/src/components/get-started/GetStartedHandleInteractionsExample.astro new file mode 100644 index 0000000..36de6f3 --- /dev/null +++ b/docs/src/components/get-started/GetStartedHandleInteractionsExample.astro @@ -0,0 +1,39 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `MapKitMapView( + state: mapViewState, + onMapLoaded: { state in + print("Loaded map \\(state.id)") + }, + onMapClick: { point in + print("Tapped \\(point.latitude), \\(point.longitude)") + }, + onCameraMoveEnd: { camera in + print("Camera ended at zoom \\(camera.zoom)") + } +) { + Marker( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + draggable: true, + onClick: { marker in + print("Marker clicked: \\(marker.id)") + }, + onDragEnd: { marker in + print("Marker dropped at \\(marker.position)") + } + ) +}`; +--- + + diff --git a/docs/src/components/get-started/GetStartedMoveCameraExample.astro b/docs/src/components/get-started/GetStartedMoveCameraExample.astro new file mode 100644 index 0000000..f0b3fa9 --- /dev/null +++ b/docs/src/components/get-started/GetStartedMoveCameraExample.astro @@ -0,0 +1,27 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; + durationMillis?: number; +} + +const { + latitude = 35.6586, + longitude = 139.7454, + zoom = 15, + durationMillis = 500, +} = Astro.props; + +const code = `mapViewState.moveCameraTo( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} + ), + durationMillis: ${durationMillis} +)`; +--- + + diff --git a/docs/src/components/get-started/GetStartedPackagesExample.astro b/docs/src/components/get-started/GetStartedPackagesExample.astro new file mode 100644 index 0000000..26df5aa --- /dev/null +++ b/docs/src/components/get-started/GetStartedPackagesExample.astro @@ -0,0 +1,18 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + coreVersion?: string; + mapkitVersion?: string; +} + +const { + coreVersion = "{CORE_MODULE_VERSION}", + mapkitVersion = "{MAPKIT_MODULE_VERSION}", +} = Astro.props; + +const code = `.package(url: "https://github.com/MapConductor/ios-sdk-core", from: "${coreVersion}"), +.package(url: "https://github.com/MapConductor/ios-for-mapkit", from: "${mapkitVersion}")`; +--- + + diff --git a/docs/src/components/get-started/GetStartedProductsExample.astro b/docs/src/components/get-started/GetStartedProductsExample.astro new file mode 100644 index 0000000..efe573a --- /dev/null +++ b/docs/src/components/get-started/GetStartedProductsExample.astro @@ -0,0 +1,8 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `.product(name: "MapConductorCore", package: "ios-sdk-core"), +.product(name: "MapConductorForMapKit", package: "ios-for-mapkit")`; +--- + + diff --git a/docs/src/components/index/Features.astro b/docs/src/components/index/Features.astro index 311fbb3..b934d43 100644 --- a/docs/src/components/index/Features.astro +++ b/docs/src/components/index/Features.astro @@ -1,5 +1,12 @@ --- import { CardGrid, Card } from '@astrojs/starlight/components'; +import { Image } from "astro:assets"; +import unifiedMapView from "~/assets/top-page/unified-map-view.png"; +import markers from "~/assets/top-page/markers.png"; +import infoBubble from "~/assets/top-page/info-bubble.png"; +import markerCluster from "~/assets/top-page/marker-cluster.png"; +import circle from "~/assets/top-page/circle.png"; +import polyline from "~/assets/top-page/polyline.png"; interface Props { titleForUnifiedAPI?: string; @@ -69,21 +76,27 @@ const { {descriptionForUnifiedAPI} + Mapview {descriptionForMarker} + Marker {descriptionForInfoBubble} + InfoBubble {descriptionForMarkerCluster} + MarkerCluster {descriptionForCircle} + Circle {descriptionForPolyline} + Polyline {descriptionForPolygon} diff --git a/docs/src/components/introduction/IntroductionMinimalExample.astro b/docs/src/components/introduction/IntroductionMinimalExample.astro new file mode 100644 index 0000000..51e5632 --- /dev/null +++ b/docs/src/components/introduction/IntroductionMinimalExample.astro @@ -0,0 +1,47 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; + circleRadiusMeters?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoom = 13, + circleRadiusMeters = 500, +} = Astro.props; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForGoogleMaps + +struct ContentView: View { + @StateObject private var mapViewState = GoogleMapViewState( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} + ) + ) + + var body: some View { + GoogleMapView(state: mapViewState) { + Marker( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + icon: DefaultMarkerIcon(label: "T") + ) + + Circle( + center: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + radiusMeters: ${circleRadiusMeters}, + fillColor: UIColor.red.withAlphaComponent(0.2) + ) + } + } +}`; +--- + + diff --git a/docs/src/components/mapviewholder/arcgis-2d/ArcGIS2DBasicUsage.astro b/docs/src/components/mapviewholder/arcgis-2d/ArcGIS2DBasicUsage.astro new file mode 100644 index 0000000..ed77b3b --- /dev/null +++ b/docs/src/components/mapviewholder/arcgis-2d/ArcGIS2DBasicUsage.astro @@ -0,0 +1,47 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; + markerLabel?: string; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoom = 14, + markerLabel = "T", +} = Astro.props; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForArcGIS + +struct ContentView: View { + @StateObject private var mapViewState = ArcGISMapViewState( + mapDesignType: ArcGISDesign.Streets, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} + ) + ) + + var body: some View { + ArcGISMapView2D( + state: mapViewState, + sdkInitialize: { + ArcGISEnvironment.apiKey = APIKey("YOUR_API_KEY") + } + ) { + Marker( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + icon: DefaultMarkerIcon(label: "${markerLabel}") + ) + } + } +}`; +--- + + diff --git a/docs/src/components/mapviewholder/arcgis-2d/ArcGIS2DInitializerParams.astro b/docs/src/components/mapviewholder/arcgis-2d/ArcGIS2DInitializerParams.astro new file mode 100644 index 0000000..e2e6cdd --- /dev/null +++ b/docs/src/components/mapviewholder/arcgis-2d/ArcGIS2DInitializerParams.astro @@ -0,0 +1,17 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `ArcGISMapView2D( + state: ArcGISMapViewState, + onMapLoaded: OnMapLoadedHandler? = nil, + onMapClick: OnMapEventHandler? = nil, + onMapLongClick: OnMapEventHandler? = nil, + onCameraMoveStart: OnCameraMoveHandler? = nil, + onCameraMove: OnCameraMoveHandler? = nil, + onCameraMoveEnd: OnCameraMoveHandler? = nil, + sdkInitialize: (() -> Void)? = nil, + content: @MapViewContentBuilder () -> MapViewContent = { MapViewContent() } +)`; +--- + + diff --git a/docs/src/components/mapviewholder/arcgis-2d/ArcGIS2DMapHolderExample.astro b/docs/src/components/mapviewholder/arcgis-2d/ArcGIS2DMapHolderExample.astro new file mode 100644 index 0000000..c6fd020 --- /dev/null +++ b/docs/src/components/mapviewholder/arcgis-2d/ArcGIS2DMapHolderExample.astro @@ -0,0 +1,12 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `import ArcGIS + +if let holder = mapViewState.getMapViewHolder(), + let map = holder.map as? ArcGIS.Map { + map.basemap = Basemap(style: .arcGISTopographic) +}`; +--- + + diff --git a/docs/src/components/mapviewholder/arcgis/ArcGISMapHolderExample.astro b/docs/src/components/mapviewholder/arcgis/ArcGISMapHolderExample.astro new file mode 100644 index 0000000..c6fd020 --- /dev/null +++ b/docs/src/components/mapviewholder/arcgis/ArcGISMapHolderExample.astro @@ -0,0 +1,12 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `import ArcGIS + +if let holder = mapViewState.getMapViewHolder(), + let map = holder.map as? ArcGIS.Map { + map.basemap = Basemap(style: .arcGISTopographic) +}`; +--- + + diff --git a/docs/src/components/mapviewholder/arcgis/ArcGISSceneHolderExample.astro b/docs/src/components/mapviewholder/arcgis/ArcGISSceneHolderExample.astro new file mode 100644 index 0000000..26ba360 --- /dev/null +++ b/docs/src/components/mapviewholder/arcgis/ArcGISSceneHolderExample.astro @@ -0,0 +1,12 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `import ArcGIS + +if let holder = mapViewState.getMapViewHolder(), + let scene = holder.map as? ArcGIS.Scene { + scene.basemap = Basemap(style: .arcGISImagery) +}`; +--- + + diff --git a/docs/src/components/mapviewholder/googlemaps/GoogleMapsHolderExample.astro b/docs/src/components/mapviewholder/googlemaps/GoogleMapsHolderExample.astro new file mode 100644 index 0000000..726d2c5 --- /dev/null +++ b/docs/src/components/mapviewholder/googlemaps/GoogleMapsHolderExample.astro @@ -0,0 +1,20 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForCast?: string; +} + +const { + commentForCast = "Access native map", +} = Astro.props; + +const code = `import GoogleMaps + +if let holder = mapViewState.getMapViewHolder(), + let mapView = holder.mapView as? GMSMapView { + mapView.mapType = .hybrid +}`; +--- + + diff --git a/docs/src/components/mapviewholder/here-maps/HereMapsHolderExample.astro b/docs/src/components/mapviewholder/here-maps/HereMapsHolderExample.astro new file mode 100644 index 0000000..eb9ce07 --- /dev/null +++ b/docs/src/components/mapviewholder/here-maps/HereMapsHolderExample.astro @@ -0,0 +1,27 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoomLevel?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoomLevel = 14, +} = Astro.props; + +const code = `import heresdk + +if let holder = mapViewState.getMapViewHolder(), + let mapView = holder.mapView as? MapView { + mapView.camera.lookAt( + point: GeoCoordinates(latitude: ${latitude}, longitude: ${longitude}), + zoom: MapMeasure(kind: .zoomLevel, value: ${zoomLevel}) + ) +}`; +--- + + diff --git a/docs/src/components/mapviewholder/index/CastMapViewExample.astro b/docs/src/components/mapviewholder/index/CastMapViewExample.astro new file mode 100644 index 0000000..1568530 --- /dev/null +++ b/docs/src/components/mapviewholder/index/CastMapViewExample.astro @@ -0,0 +1,21 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForCast?: string; +} + +const { + commentForCast = "Access native Google Maps view", +} = Astro.props; + +const code = `import GoogleMaps + +if let holder = mapViewState.getMapViewHolder(), + let googleMap = holder.mapView as? GMSMapView { + // ${commentForCast} + googleMap.mapType = .hybrid +}`; +--- + + diff --git a/docs/src/components/mapviewholder/index/GuardHolderExample.astro b/docs/src/components/mapviewholder/index/GuardHolderExample.astro new file mode 100644 index 0000000..97b693e --- /dev/null +++ b/docs/src/components/mapviewholder/index/GuardHolderExample.astro @@ -0,0 +1,9 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `guard let holder = mapViewState.getMapViewHolder() else { + return +}`; +--- + + diff --git a/docs/src/components/mapviewholder/index/ScreenOffsetExample.astro b/docs/src/components/mapviewholder/index/ScreenOffsetExample.astro new file mode 100644 index 0000000..6cfc886 --- /dev/null +++ b/docs/src/components/mapviewholder/index/ScreenOffsetExample.astro @@ -0,0 +1,21 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, +} = Astro.props; + +const code = `if let holder = mapViewState.getMapViewHolder() { + let screenPoint = holder.toScreenOffset( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}) + ) +}`; +--- + + diff --git a/docs/src/components/mapviewholder/mapbox/MapboxHolderExample.astro b/docs/src/components/mapviewholder/mapbox/MapboxHolderExample.astro new file mode 100644 index 0000000..4d534c2 --- /dev/null +++ b/docs/src/components/mapviewholder/mapbox/MapboxHolderExample.astro @@ -0,0 +1,29 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoom?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoom = 14, +} = Astro.props; + +const code = `import MapboxMaps + +if let holder = mapViewState.getMapViewHolder(), + let mapView = holder.mapView as? MapView { + mapView.mapboxMap.setCamera( + to: CameraOptions( + center: CLLocationCoordinate2D(latitude: ${latitude}, longitude: ${longitude}), + zoom: ${zoom} + ) + ) +}`; +--- + + diff --git a/docs/src/components/mapviewholder/mapkit/MapKitDesignTypeExample.astro b/docs/src/components/mapviewholder/mapkit/MapKitDesignTypeExample.astro new file mode 100644 index 0000000..f06c42a --- /dev/null +++ b/docs/src/components/mapviewholder/mapkit/MapKitDesignTypeExample.astro @@ -0,0 +1,7 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `mapViewState.mapDesignType = MapKitMapDesign.Hybrid`; +--- + + diff --git a/docs/src/components/mapviewholder/mapkit/MapKitHolderExample.astro b/docs/src/components/mapviewholder/mapkit/MapKitHolderExample.astro new file mode 100644 index 0000000..65752bf --- /dev/null +++ b/docs/src/components/mapviewholder/mapkit/MapKitHolderExample.astro @@ -0,0 +1,12 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `import MapKit + +if let holder = mapViewState.getMapViewHolder(), + let mapView = holder.mapView as? MKMapView { + mapView.showsTraffic = true +}`; +--- + + diff --git a/docs/src/components/mapviewholder/maplibre/MapLibreHolderExample.astro b/docs/src/components/mapviewholder/maplibre/MapLibreHolderExample.astro new file mode 100644 index 0000000..b2ad59a --- /dev/null +++ b/docs/src/components/mapviewholder/maplibre/MapLibreHolderExample.astro @@ -0,0 +1,28 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + zoomLevel?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + zoomLevel = 14, +} = Astro.props; + +const code = `import MapLibre + +if let holder = mapViewState.getMapViewHolder(), + let mapView = holder.mapView as? MLNMapView { + mapView.setCenter( + CLLocationCoordinate2D(latitude: ${latitude}, longitude: ${longitude}), + zoomLevel: ${zoomLevel}, + animated: true + ) +}`; +--- + + diff --git a/docs/src/components/modules/ModulesOptionalPackagesExample.astro b/docs/src/components/modules/ModulesOptionalPackagesExample.astro new file mode 100644 index 0000000..efaadfe --- /dev/null +++ b/docs/src/components/modules/ModulesOptionalPackagesExample.astro @@ -0,0 +1,37 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + coreVersion?: string; + googlemapsVersion?: string; + heatmapVersion?: string; + markerClusterVersion?: string; +} + +const { + coreVersion = "{CORE_MODULE_VERSION}", + googlemapsVersion = "{GOOGLEMAPS_MODULE_VERSION}", + heatmapVersion = "{HEATMAP_MODULE_VERSION}", + markerClusterVersion = "{MARKER_CLUSTERING_MODULE_VERSION}", +} = Astro.props; + +const code = `dependencies: [ + .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "${coreVersion}"), + .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "${googlemapsVersion}"), + .package(url: "https://github.com/MapConductor/ios-heatmap", from: "${heatmapVersion}"), + .package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "${markerClusterVersion}"), +], +targets: [ + .target( + name: "App", + dependencies: [ + .product(name: "MapConductorCore", package: "ios-sdk-core"), + .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), + .product(name: "MapConductorHeatmap", package: "ios-heatmap"), + .product(name: "MapConductorMarkerCluster", package: "ios-marker-cluster"), + ] + ) +]`; +--- + + diff --git a/docs/src/components/modules/ModulesPackageSwiftExample.astro b/docs/src/components/modules/ModulesPackageSwiftExample.astro new file mode 100644 index 0000000..5df8297 --- /dev/null +++ b/docs/src/components/modules/ModulesPackageSwiftExample.astro @@ -0,0 +1,29 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + coreVersion?: string; + googlemapsVersion?: string; +} + +const { + coreVersion = "{CORE_MODULE_VERSION}", + googlemapsVersion = "{GOOGLEMAPS_MODULE_VERSION}", +} = Astro.props; + +const code = `dependencies: [ + .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "${coreVersion}"), + .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "${googlemapsVersion}"), +], +targets: [ + .target( + name: "App", + dependencies: [ + .product(name: "MapConductorCore", package: "ios-sdk-core"), + .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), + ] + ) +]`; +--- + + diff --git a/docs/src/components/setup/arcgis/ArcGISCustomDesignExample.astro b/docs/src/components/setup/arcgis/ArcGISCustomDesignExample.astro new file mode 100644 index 0000000..a7ff662 --- /dev/null +++ b/docs/src/components/setup/arcgis/ArcGISCustomDesignExample.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + designId?: string; +} + +const { + designId = "osm_standard", +} = Astro.props; + +const code = `mapState.mapDesignType = ArcGISDesign.Create(id: "${designId}")`; +--- + + diff --git a/docs/src/components/setup/arcgis/ArcGISInitExample.astro b/docs/src/components/setup/arcgis/ArcGISInitExample.astro new file mode 100644 index 0000000..16b063d --- /dev/null +++ b/docs/src/components/setup/arcgis/ArcGISInitExample.astro @@ -0,0 +1,29 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + apiKey?: string; +} + +const { + apiKey = "YOUR_ARCGIS_API_KEY", +} = Astro.props; + +const code = `import SwiftUI +import MapConductorForArcGIS + +@main +struct MyApp: App { + init() { + _ = arcGISApiKeyInitialize(apiKey: "${apiKey}") + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +}`; +--- + + diff --git a/docs/src/components/setup/arcgis/ArcGISInitSdkInitializeExample.astro b/docs/src/components/setup/arcgis/ArcGISInitSdkInitializeExample.astro new file mode 100644 index 0000000..5800213 --- /dev/null +++ b/docs/src/components/setup/arcgis/ArcGISInitSdkInitializeExample.astro @@ -0,0 +1,18 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + apiKey?: string; +} + +const { + apiKey = "YOUR_ARCGIS_API_KEY", +} = Astro.props; + +const code = `ArcGISMapView( + state: mapState, + sdkInitialize: { _ = arcGISApiKeyInitialize(apiKey: "${apiKey}") } +) { ... }`; +--- + + diff --git a/docs/src/components/setup/arcgis/ArcGISStylePresetsExample.astro b/docs/src/components/setup/arcgis/ArcGISStylePresetsExample.astro new file mode 100644 index 0000000..1c5eae0 --- /dev/null +++ b/docs/src/components/setup/arcgis/ArcGISStylePresetsExample.astro @@ -0,0 +1,41 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentStreets?: string; + commentImagery?: string; + commentTopographic?: string; + commentOsmStandard?: string; + commentNavigationNight?: string; + commentDarkGray?: string; +} + +const { + commentStreets = "Streets map (default)", + commentImagery = "Satellite imagery", + commentTopographic = "Topographic map", + commentOsmStandard = "OpenStreetMap standard style", + commentNavigationNight = "Night navigation", + commentDarkGray = "Dark gray", +} = Astro.props; + +const code = `// ${commentStreets} +mapState.mapDesignType = ArcGISDesign.Streets + +// ${commentImagery} +mapState.mapDesignType = ArcGISDesign.Imagery + +// ${commentTopographic} +mapState.mapDesignType = ArcGISDesign.Topographic + +// ${commentOsmStandard} +mapState.mapDesignType = ArcGISDesign.OsmStandard + +// ${commentNavigationNight} +mapState.mapDesignType = ArcGISDesign.NavigationNight + +// ${commentDarkGray} +mapState.mapDesignType = ArcGISDesign.DarkGray`; +--- + + diff --git a/docs/src/components/setup/arcgis/ArcGISVerification2DExample.astro b/docs/src/components/setup/arcgis/ArcGISVerification2DExample.astro new file mode 100644 index 0000000..32a76c2 --- /dev/null +++ b/docs/src/components/setup/arcgis/ArcGISVerification2DExample.astro @@ -0,0 +1,8 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `ArcGISMapView2D(state: mapState) + .ignoresSafeArea()`; +--- + + diff --git a/docs/src/components/setup/arcgis/ArcGISVerification3DExample.astro b/docs/src/components/setup/arcgis/ArcGISVerification3DExample.astro new file mode 100644 index 0000000..f9b4c1e --- /dev/null +++ b/docs/src/components/setup/arcgis/ArcGISVerification3DExample.astro @@ -0,0 +1,26 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForArcGIS + +struct ContentView: View { + @StateObject var mapState = ArcGISMapViewState( + mapDesignType: ArcGISDesign.Streets, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 12.0, + tilt: 45.0, + bearing: 0.0 + ) + ) + + var body: some View { + ArcGISMapView(state: mapState) + .ignoresSafeArea() + } +}`; +--- + + diff --git a/docs/src/components/setup/google-maps/GoogleMapsInitExample.astro b/docs/src/components/setup/google-maps/GoogleMapsInitExample.astro new file mode 100644 index 0000000..3b5f845 --- /dev/null +++ b/docs/src/components/setup/google-maps/GoogleMapsInitExample.astro @@ -0,0 +1,30 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + apiKey?: string; +} + +const { + apiKey = "YOUR_GOOGLE_MAPS_API_KEY", +} = Astro.props; + +const code = `import SwiftUI +import GoogleMaps +import MapConductorForGoogleMaps + +@main +struct MyApp: App { + init() { + GMSServices.provideAPIKey("${apiKey}") + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +}`; +--- + + diff --git a/docs/src/components/setup/google-maps/GoogleMapsInitSdkInitializeExample.astro b/docs/src/components/setup/google-maps/GoogleMapsInitSdkInitializeExample.astro new file mode 100644 index 0000000..ca49d8e --- /dev/null +++ b/docs/src/components/setup/google-maps/GoogleMapsInitSdkInitializeExample.astro @@ -0,0 +1,18 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + apiKey?: string; +} + +const { + apiKey = "YOUR_GOOGLE_MAPS_API_KEY", +} = Astro.props; + +const code = `GoogleMapView( + state: mapState, + sdkInitialize: { GMSServices.provideAPIKey("${apiKey}") } +) { ... }`; +--- + + diff --git a/docs/src/components/setup/google-maps/GoogleMapsVerificationExample.astro b/docs/src/components/setup/google-maps/GoogleMapsVerificationExample.astro new file mode 100644 index 0000000..f7ee213 --- /dev/null +++ b/docs/src/components/setup/google-maps/GoogleMapsVerificationExample.astro @@ -0,0 +1,23 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForGoogleMaps + +struct ContentView: View { + @StateObject var mapState = GoogleMapViewState( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 12.0 + ) + ) + + var body: some View { + GoogleMapView(state: mapState) + .ignoresSafeArea() + } +}`; +--- + + diff --git a/docs/src/components/setup/here-maps/HereMapsInitExample.astro b/docs/src/components/setup/here-maps/HereMapsInitExample.astro new file mode 100644 index 0000000..f3fd8d3 --- /dev/null +++ b/docs/src/components/setup/here-maps/HereMapsInitExample.astro @@ -0,0 +1,40 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + accessKeyId?: string; + accessKeySecret?: string; + commentFailure?: string; +} + +const { + accessKeyId = "YOUR_HERE_ACCESS_KEY_ID", + accessKeySecret = "YOUR_HERE_ACCESS_KEY_SECRET", + commentFailure = "Failed to initialize HERE SDK: ", +} = Astro.props; + +const code = `import SwiftUI +import MapConductorForHERE + +@main +struct MyApp: App { + init() { + do { + try initializeHERE( + accessKeyId: "${accessKeyId}", + accessKeySecret: "${accessKeySecret}" + ) + } catch { + assertionFailure("${commentFailure}\\(error)") + } + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +}`; +--- + + diff --git a/docs/src/components/setup/here-maps/HereMapsInitSdkInitializeExample.astro b/docs/src/components/setup/here-maps/HereMapsInitSdkInitializeExample.astro new file mode 100644 index 0000000..f87d847 --- /dev/null +++ b/docs/src/components/setup/here-maps/HereMapsInitSdkInitializeExample.astro @@ -0,0 +1,25 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + accessKeyId?: string; + accessKeySecret?: string; +} + +const { + accessKeyId = "YOUR_HERE_ACCESS_KEY_ID", + accessKeySecret = "YOUR_HERE_ACCESS_KEY_SECRET", +} = Astro.props; + +const code = `HereMapView( + state: mapState, + sdkInitialize: { + try? initializeHERE( + accessKeyId: "${accessKeyId}", + accessKeySecret: "${accessKeySecret}" + ) + } +) { ... }`; +--- + + diff --git a/docs/src/components/setup/here-maps/HereMapsStylePresetsExample.astro b/docs/src/components/setup/here-maps/HereMapsStylePresetsExample.astro new file mode 100644 index 0000000..6cacfdf --- /dev/null +++ b/docs/src/components/setup/here-maps/HereMapsStylePresetsExample.astro @@ -0,0 +1,46 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentNormalDay?: string; + commentNormalNight?: string; + commentSatellite?: string; + commentHybridDay?: string; + commentLiteDay?: string; + commentLogisticsDay?: string; + commentRoadNetworkDay?: string; +} + +const { + commentNormalDay = "Normal day style (default)", + commentNormalNight = "Normal night style", + commentSatellite = "Satellite imagery", + commentHybridDay = "Satellite imagery with roads and labels", + commentLiteDay = "Lightweight day style", + commentLogisticsDay = "Logistics style", + commentRoadNetworkDay = "Road network style", +} = Astro.props; + +const code = `// ${commentNormalDay} +mapState.mapDesignType = HereMapDesign.NormalDay + +// ${commentNormalNight} +mapState.mapDesignType = HereMapDesign.NormalNight + +// ${commentSatellite} +mapState.mapDesignType = HereMapDesign.Satellite + +// ${commentHybridDay} +mapState.mapDesignType = HereMapDesign.HybridDay + +// ${commentLiteDay} +mapState.mapDesignType = HereMapDesign.LiteDay + +// ${commentLogisticsDay} +mapState.mapDesignType = HereMapDesign.LogisticsDay + +// ${commentRoadNetworkDay} +mapState.mapDesignType = HereMapDesign.RoadNetworkDay`; +--- + + diff --git a/docs/src/components/setup/here-maps/HereMapsVerificationExample.astro b/docs/src/components/setup/here-maps/HereMapsVerificationExample.astro new file mode 100644 index 0000000..fcaf6f5 --- /dev/null +++ b/docs/src/components/setup/here-maps/HereMapsVerificationExample.astro @@ -0,0 +1,24 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForHERE + +struct ContentView: View { + @StateObject var mapState = HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 12.0 + ) + ) + + var body: some View { + HereMapView(state: mapState) + .ignoresSafeArea() + } +}`; +--- + + diff --git a/docs/src/components/setup/mapbox/MapboxCustomStyleExample.astro b/docs/src/components/setup/mapbox/MapboxCustomStyleExample.astro new file mode 100644 index 0000000..f6cf220 --- /dev/null +++ b/docs/src/components/setup/mapbox/MapboxCustomStyleExample.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + styleURI?: string; +} + +const { + styleURI = "mapbox://styles/user/abc123", +} = Astro.props; + +const code = `mapState.mapDesignType = MapboxMapDesign.custom(styleURI: "${styleURI}")`; +--- + + diff --git a/docs/src/components/setup/mapbox/MapboxInitExample.astro b/docs/src/components/setup/mapbox/MapboxInitExample.astro new file mode 100644 index 0000000..d6c7cef --- /dev/null +++ b/docs/src/components/setup/mapbox/MapboxInitExample.astro @@ -0,0 +1,29 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + accessToken?: string; +} + +const { + accessToken = "YOUR_MAPBOX_PUBLIC_TOKEN", +} = Astro.props; + +const code = `import SwiftUI +import MapConductorForMapbox + +@main +struct MyApp: App { + init() { + initializeMapbox(accessToken: "${accessToken}") + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +}`; +--- + + diff --git a/docs/src/components/setup/mapbox/MapboxInitSdkInitializeExample.astro b/docs/src/components/setup/mapbox/MapboxInitSdkInitializeExample.astro new file mode 100644 index 0000000..1ef6e0c --- /dev/null +++ b/docs/src/components/setup/mapbox/MapboxInitSdkInitializeExample.astro @@ -0,0 +1,18 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + accessToken?: string; +} + +const { + accessToken = "YOUR_MAPBOX_PUBLIC_TOKEN", +} = Astro.props; + +const code = `MapboxMapView( + state: mapState, + sdkInitialize: { initializeMapbox(accessToken: "${accessToken}") } +) { ... }`; +--- + + diff --git a/docs/src/components/setup/mapbox/MapboxStylePresetsExample.astro b/docs/src/components/setup/mapbox/MapboxStylePresetsExample.astro new file mode 100644 index 0000000..c13bb17 --- /dev/null +++ b/docs/src/components/setup/mapbox/MapboxStylePresetsExample.astro @@ -0,0 +1,31 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentStandard?: string; + commentStreets?: string; + commentSatelliteStreets?: string; + commentDark?: string; +} + +const { + commentStandard = "Standard style (default)", + commentStreets = "Streets style", + commentSatelliteStreets = "Satellite imagery with roads and labels", + commentDark = "Dark style", +} = Astro.props; + +const code = `// ${commentStandard} +mapState.mapDesignType = MapboxMapDesign.Standard + +// ${commentStreets} +mapState.mapDesignType = MapboxMapDesign.Streets + +// ${commentSatelliteStreets} +mapState.mapDesignType = MapboxMapDesign.SatelliteStreets + +// ${commentDark} +mapState.mapDesignType = MapboxMapDesign.Dark`; +--- + + diff --git a/docs/src/components/setup/mapbox/MapboxVerificationExample.astro b/docs/src/components/setup/mapbox/MapboxVerificationExample.astro new file mode 100644 index 0000000..22e407f --- /dev/null +++ b/docs/src/components/setup/mapbox/MapboxVerificationExample.astro @@ -0,0 +1,24 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForMapbox + +struct ContentView: View { + @StateObject var mapState = MapboxViewState( + mapDesignType: MapboxMapDesign.Standard, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 12.0 + ) + ) + + var body: some View { + MapboxMapView(state: mapState) + .ignoresSafeArea() + } +}`; +--- + + diff --git a/docs/src/components/setup/mapkit/MapKitStylePresetsExample.astro b/docs/src/components/setup/mapkit/MapKitStylePresetsExample.astro new file mode 100644 index 0000000..d9785d6 --- /dev/null +++ b/docs/src/components/setup/mapkit/MapKitStylePresetsExample.astro @@ -0,0 +1,31 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentStandard?: string; + commentSatellite?: string; + commentHybrid?: string; + commentMutedStandard?: string; +} + +const { + commentStandard = "Standard street map", + commentSatellite = "Satellite imagery", + commentHybrid = "Satellite with roads and labels", + commentMutedStandard = "Muted standard style", +} = Astro.props; + +const code = `// ${commentStandard} +mapState.mapDesignType = MapKitMapDesign.Standard + +// ${commentSatellite} +mapState.mapDesignType = MapKitMapDesign.Satellite + +// ${commentHybrid} +mapState.mapDesignType = MapKitMapDesign.Hybrid + +// ${commentMutedStandard} +mapState.mapDesignType = MapKitMapDesign.MutedStandard`; +--- + + diff --git a/docs/src/components/setup/mapkit/MapKitVerificationExample.astro b/docs/src/components/setup/mapkit/MapKitVerificationExample.astro new file mode 100644 index 0000000..7442b4e --- /dev/null +++ b/docs/src/components/setup/mapkit/MapKitVerificationExample.astro @@ -0,0 +1,37 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentSdkInitialize?: string; +} + +const { + commentSdkInitialize = "Optional. Called once before MKMapView is created.", +} = Astro.props; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForMapKit + +struct ContentView: View { + @StateObject var mapState = MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 12.0 + ) + ) + + var body: some View { + MapKitMapView( + state: mapState, + sdkInitialize: { + // ${commentSdkInitialize} + } + ) + .ignoresSafeArea() + } +}`; +--- + + diff --git a/docs/src/components/setup/maplibre/MapLibreCustomStyleExample.astro b/docs/src/components/setup/maplibre/MapLibreCustomStyleExample.astro new file mode 100644 index 0000000..ec17b1d --- /dev/null +++ b/docs/src/components/setup/maplibre/MapLibreCustomStyleExample.astro @@ -0,0 +1,19 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + styleJsonURL?: string; +} + +const { + styleJsonURL = "https://example.com/style.json", +} = Astro.props; + +const code = `let custom = MapLibreDesign( + id: "custom", + styleJsonURL: "${styleJsonURL}" +) +mapState.mapDesignType = custom`; +--- + + diff --git a/docs/src/components/setup/maplibre/MapLibreStylePresetsExample.astro b/docs/src/components/setup/maplibre/MapLibreStylePresetsExample.astro new file mode 100644 index 0000000..a6983cd --- /dev/null +++ b/docs/src/components/setup/maplibre/MapLibreStylePresetsExample.astro @@ -0,0 +1,31 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentOsmBright?: string; + commentOsmBrightJa?: string; + commentMapTilerToner?: string; + commentDemoTiles?: string; +} + +const { + commentOsmBright = "OpenStreetMap Bright (English)", + commentOsmBrightJa = "OpenStreetMap Bright (Japanese)", + commentMapTilerToner = "MapTiler Toner (English)", + commentDemoTiles = "Lightweight demo tiles", +} = Astro.props; + +const code = `// ${commentOsmBright} +mapState.mapDesignType = MapLibreDesign.OsmBright + +// ${commentOsmBrightJa} +mapState.mapDesignType = MapLibreDesign.OsmBrightJa + +// ${commentMapTilerToner} +mapState.mapDesignType = MapLibreDesign.MapTilerTonerEn + +// ${commentDemoTiles} +mapState.mapDesignType = MapLibreDesign.DemoTiles`; +--- + + diff --git a/docs/src/components/setup/maplibre/MapLibreVerificationExample.astro b/docs/src/components/setup/maplibre/MapLibreVerificationExample.astro new file mode 100644 index 0000000..99e5f48 --- /dev/null +++ b/docs/src/components/setup/maplibre/MapLibreVerificationExample.astro @@ -0,0 +1,24 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForMapLibre + +struct ContentView: View { + @StateObject var mapState = MapLibreViewState( + mapDesignType: MapLibreDesign.OsmBright, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 12.0 + ) + ) + + var body: some View { + MapLibreMapView(state: mapState) + .ignoresSafeArea() + } +}`; +--- + + diff --git a/docs/src/components/setup/overview/MinimalGoogleMapsExample.astro b/docs/src/components/setup/overview/MinimalGoogleMapsExample.astro new file mode 100644 index 0000000..7098f74 --- /dev/null +++ b/docs/src/components/setup/overview/MinimalGoogleMapsExample.astro @@ -0,0 +1,35 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentMarker?: string; +} + +const { + commentMarker = "T", +} = Astro.props; + +const code = `import SwiftUI +import MapConductorCore +import MapConductorForGoogleMaps + +struct ContentView: View { + @StateObject private var mapViewState = GoogleMapViewState( + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 13 + ) + ) + + var body: some View { + GoogleMapView(state: mapViewState) { + Marker( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + icon: DefaultMarkerIcon(label: "${commentMarker}") + ) + } + } +}`; +--- + + diff --git a/docs/src/components/setup/overview/PackageExample.astro b/docs/src/components/setup/overview/PackageExample.astro new file mode 100644 index 0000000..eefcba0 --- /dev/null +++ b/docs/src/components/setup/overview/PackageExample.astro @@ -0,0 +1,18 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + coreVersion?: string; + googlemapsVersion?: string; +} + +const { + coreVersion = "{CORE_MODULE_VERSION}", + googlemapsVersion = "{GOOGLEMAPS_MODULE_VERSION}", +} = Astro.props; + +const code = `.package(url: "https://github.com/MapConductor/ios-sdk-core", from: "${coreVersion}"), +.package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "${googlemapsVersion}")`; +--- + + diff --git a/docs/src/components/setup/shared/LocationManagerExample.astro b/docs/src/components/setup/shared/LocationManagerExample.astro new file mode 100644 index 0000000..5f9e2bb --- /dev/null +++ b/docs/src/components/setup/shared/LocationManagerExample.astro @@ -0,0 +1,17 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `import CoreLocation + +class LocationManager: NSObject, CLLocationManagerDelegate { + let manager = CLLocationManager() + + override init() { + super.init() + manager.delegate = self + manager.requestWhenInUseAuthorization() + } +}`; +--- + + diff --git a/docs/src/components/states/circle-state/CircleStateBasicExample.astro b/docs/src/components/states/circle-state/CircleStateBasicExample.astro new file mode 100644 index 0000000..4cc051c --- /dev/null +++ b/docs/src/components/states/circle-state/CircleStateBasicExample.astro @@ -0,0 +1,34 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + latitude?: number; + longitude?: number; + initialRadius?: number; + updatedLatitude?: number; + updatedLongitude?: number; + updatedRadius?: number; +} + +const { + latitude = 35.6812, + longitude = 139.7671, + initialRadius = 500, + updatedLatitude = 35.6895, + updatedLongitude = 139.6917, + updatedRadius = 1000, +} = Astro.props; + +const code = `let circleState = CircleState( + center: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + radiusMeters: ${initialRadius}, + fillColor: UIColor.blue.withAlphaComponent(0.2) +) + +Circle(state: circleState) + +circleState.radiusMeters = ${updatedRadius} +circleState.center = GeoPoint(latitude: ${updatedLatitude}, longitude: ${updatedLongitude})`; +--- + + diff --git a/docs/src/components/states/circle-state/CircleStateSelectionExample.astro b/docs/src/components/states/circle-state/CircleStateSelectionExample.astro new file mode 100644 index 0000000..23ae641 --- /dev/null +++ b/docs/src/components/states/circle-state/CircleStateSelectionExample.astro @@ -0,0 +1,11 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `circleState.onClick = { event in + selectedCircleId = event.state.id +} + +circleState.strokeColor = selectedCircleId == circleState.id ? .red : .blue`; +--- + + diff --git a/docs/src/components/states/groundimage-state/GroundImageStateBasicExample.astro b/docs/src/components/states/groundimage-state/GroundImageStateBasicExample.astro new file mode 100644 index 0000000..21380aa --- /dev/null +++ b/docs/src/components/states/groundimage-state/GroundImageStateBasicExample.astro @@ -0,0 +1,38 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + swLat?: number; + swLon?: number; + neLat?: number; + neLon?: number; + initialOpacity?: number; + updatedOpacity?: number; +} + +const { + swLat = 35.67, + swLon = 139.74, + neLat = 35.69, + neLon = 139.76, + initialOpacity = 0.5, + updatedOpacity = 1.0, +} = Astro.props; + +const code = `let bounds = GeoRectBounds( + southWest: GeoPoint(latitude: ${swLat}, longitude: ${swLon}), + northEast: GeoPoint(latitude: ${neLat}, longitude: ${neLon}) +) + +let groundImageState = GroundImageState( + bounds: bounds, + image: UIImage(named: "overlay")!, + opacity: ${initialOpacity} +) + +GroundImage(state: groundImageState) + +groundImageState.opacity = ${updatedOpacity}`; +--- + + diff --git a/docs/src/components/states/groundimage-state/GroundImageStateRepositionExample.astro b/docs/src/components/states/groundimage-state/GroundImageStateRepositionExample.astro new file mode 100644 index 0000000..8d17d28 --- /dev/null +++ b/docs/src/components/states/groundimage-state/GroundImageStateRepositionExample.astro @@ -0,0 +1,24 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + swLat?: number; + swLon?: number; + neLat?: number; + neLon?: number; +} + +const { + swLat = 35.66, + swLon = 139.73, + neLat = 35.70, + neLon = 139.77, +} = Astro.props; + +const code = `groundImageState.bounds = GeoRectBounds( + southWest: GeoPoint(latitude: ${swLat}, longitude: ${swLon}), + northEast: GeoPoint(latitude: ${neLat}, longitude: ${neLon}) +)`; +--- + + diff --git a/docs/src/components/states/marker-state/MarkerStateBasicExample.astro b/docs/src/components/states/marker-state/MarkerStateBasicExample.astro new file mode 100644 index 0000000..adf4b24 --- /dev/null +++ b/docs/src/components/states/marker-state/MarkerStateBasicExample.astro @@ -0,0 +1,36 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForClick?: string; + latitude?: number; + longitude?: number; + updatedLatitude?: number; + updatedLongitude?: number; +} + +const { + commentForClick = "Clicked", + latitude = 35.6812, + longitude = 139.7671, + updatedLatitude = 35.6895, + updatedLongitude = 139.6917, +} = Astro.props; + +const code = `let markerState = MarkerState( + position: GeoPoint(latitude: ${latitude}, longitude: ${longitude}), + extra: "Tokyo Station", + icon: DefaultMarkerIcon(fillColor: .red, label: "T"), + draggable: true, + onClick: { state in + print("${commentForClick} \\(state.id), extra: \\(String(describing: state.extra))") + } +) + +Marker(state: markerState) + +markerState.position = GeoPoint(latitude: ${updatedLatitude}, longitude: ${updatedLongitude}) +markerState.animate(.Bounce)`; +--- + + diff --git a/docs/src/components/states/marker-state/MarkerStateHandlersExample.astro b/docs/src/components/states/marker-state/MarkerStateHandlersExample.astro new file mode 100644 index 0000000..d81616a --- /dev/null +++ b/docs/src/components/states/marker-state/MarkerStateHandlersExample.astro @@ -0,0 +1,23 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + commentForOnClick?: string; + commentForOnDragEnd?: string; +} + +const { + commentForOnClick = "selectedMarkerId = state.id", + commentForOnDragEnd = "saveCoordinate(state.position)", +} = Astro.props; + +const code = `markerState.onClick = { state in + ${commentForOnClick} +} + +markerState.onDragEnd = { state in + ${commentForOnDragEnd} +}`; +--- + + diff --git a/docs/src/components/states/polygon-state/PolygonStateBasicExample.astro b/docs/src/components/states/polygon-state/PolygonStateBasicExample.astro new file mode 100644 index 0000000..0eee277 --- /dev/null +++ b/docs/src/components/states/polygon-state/PolygonStateBasicExample.astro @@ -0,0 +1,24 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + updatedStrokeWidth?: number; +} + +const { + updatedStrokeWidth = 3, +} = Astro.props; + +const code = `let polygonState = PolygonState( + points: outerRing, + fillColor: UIColor.green.withAlphaComponent(0.25), + holes: [innerRing] +) + +Polygon(state: polygonState) + +polygonState.strokeWidth = ${updatedStrokeWidth} +polygonState.fillColor = UIColor.red.withAlphaComponent(0.2)`; +--- + + diff --git a/docs/src/components/states/polygon-state/PolygonStateHolesExample.astro b/docs/src/components/states/polygon-state/PolygonStateHolesExample.astro new file mode 100644 index 0000000..b69fda2 --- /dev/null +++ b/docs/src/components/states/polygon-state/PolygonStateHolesExample.astro @@ -0,0 +1,7 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +const code = `polygonState.holes = [restrictedAreaRing]`; +--- + + diff --git a/docs/src/components/states/polyline-state/PolylineStateBasicExample.astro b/docs/src/components/states/polyline-state/PolylineStateBasicExample.astro new file mode 100644 index 0000000..2ec6910 --- /dev/null +++ b/docs/src/components/states/polyline-state/PolylineStateBasicExample.astro @@ -0,0 +1,39 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + lat1?: number; + lon1?: number; + lat2?: number; + lon2?: number; + strokeWidth?: number; + appendLat?: number; + appendLon?: number; +} + +const { + lat1 = 35.6812, + lon1 = 139.7671, + lat2 = 35.6586, + lon2 = 139.7454, + strokeWidth = 4, + appendLat = 35.7101, + appendLon = 139.8107, +} = Astro.props; + +const code = `let polylineState = PolylineState( + points: [ + GeoPoint(latitude: ${lat1}, longitude: ${lon1}), + GeoPoint(latitude: ${lat2}, longitude: ${lon2}) + ], + strokeColor: .blue, + strokeWidth: ${strokeWidth} +) + +Polyline(state: polylineState) + +polylineState.points.append(GeoPoint(latitude: ${appendLat}, longitude: ${appendLon})) +polylineState.strokeColor = .red`; +--- + + diff --git a/docs/src/components/states/polyline-state/PolylineStateRouteUpdateExample.astro b/docs/src/components/states/polyline-state/PolylineStateRouteUpdateExample.astro new file mode 100644 index 0000000..51975c7 --- /dev/null +++ b/docs/src/components/states/polyline-state/PolylineStateRouteUpdateExample.astro @@ -0,0 +1,16 @@ +--- +import { Code } from '@astrojs/starlight/components'; + +interface Props { + strokeWidth?: number; +} + +const { + strokeWidth = 5, +} = Astro.props; + +const code = `polylineState.points = recalculatedRoute +polylineState.strokeWidth = ${strokeWidth}`; +--- + + diff --git a/docs/src/content/docs/components/circle.mdx b/docs/src/content/docs/components/circle.mdx index becd72b..15f8d7e 100644 --- a/docs/src/content/docs/components/circle.mdx +++ b/docs/src/content/docs/components/circle.mdx @@ -7,40 +7,20 @@ head: content: Draw circular overlays with configurable radius, fill, and stroke using Circle in MapConductor iOS SDK. --- +import CircleBasicExample from '~/components/components/circle/CircleBasicExample.astro'; +import CircleInitializer from '~/components/components/circle/CircleInitializer.astro'; +import CircleClickExample from '~/components/components/circle/CircleClickExample.astro'; +import CircleStateExample from '~/components/components/circle/CircleStateExample.astro'; + `Circle` draws a circular overlay. Use circles for radius-based areas such as service zones, search ranges, or geofences. The radius is expressed in meters through `radiusMeters`. -```swift -Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 1_000, - strokeColor: .red, - strokeWidth: 2, - fillColor: UIColor.red.withAlphaComponent(0.2), - onClick: { event in - print("Clicked at \(event.clicked)") - } -) -``` + ## Initializer -```swift -Circle( - center: GeoPointProtocol, - radiusMeters: Double, - geodesic: Bool = true, - clickable: Bool = true, - strokeColor: UIColor = .red, - strokeWidth: Double = 1.0, - fillColor: UIColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5), - id: String? = nil, - zIndex: Int? = nil, - extra: Any? = nil, - onClick: OnCircleEventHandler? = nil -) -``` + Use the alpha component of `fillColor` or `strokeColor` for transparency. @@ -48,29 +28,10 @@ Use the alpha component of `fillColor` or `strokeColor` for transparency. Set `clickable` and `onClick` when the user should be able to select or inspect the circle. The event contains the clicked coordinate: -```swift -Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 750, - clickable: true, - onClick: { event in - print("Circle clicked at \(event.clicked)") - } -) -``` + ## Updating State -```swift -let state = CircleState( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500 -) - -Circle(state: state) - -state.radiusMeters = 1_000 -state.fillColor = UIColor.blue.withAlphaComponent(0.2) -``` + Use a state object when the radius, center, colors, z-index, or click handler changes after the overlay is created. diff --git a/docs/src/content/docs/components/groundimage.mdx b/docs/src/content/docs/components/groundimage.mdx index 2a901e3..14be54f 100644 --- a/docs/src/content/docs/components/groundimage.mdx +++ b/docs/src/content/docs/components/groundimage.mdx @@ -7,59 +7,31 @@ head: content: Overlay georeferenced images on the map using GroundImage in MapConductor iOS SDK. --- +import GroundImageBasicExample from '~/components/components/groundimage/GroundImageBasicExample.astro'; +import GroundImageInitializer from '~/components/components/groundimage/GroundImageInitializer.astro'; +import GroundImageStateExample from '~/components/components/groundimage/GroundImageStateExample.astro'; +import GroundImageOpacityExample from '~/components/components/groundimage/GroundImageOpacityExample.astro'; +import GroundImageClickExample from '~/components/components/groundimage/GroundImageClickExample.astro'; + `GroundImage` places a `UIImage` over a geographic rectangle. Use ground images for fixed geographic overlays such as floor plans, historical maps, diagrams, or custom raster annotations. The image is positioned by `GeoRectBounds`. -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) - -GroundImage( - bounds: bounds, - image: UIImage(named: "overlay")!, - opacity: 0.7, - tileSize: 512, - onClick: { event in - print("Ground image clicked: \(String(describing: event.clicked))") - } -) -``` + ## Initializer -```swift -GroundImage( - bounds: GeoRectBounds, - image: UIImage, - opacity: Double = 1.0, - tileSize: Int = 512, - id: String? = nil, - extra: Any? = nil, - onClick: OnGroundImageEventHandler? = nil -) -``` + ## Updating State -```swift -let state = GroundImageState(bounds: bounds, image: image, opacity: 0.5) - -GroundImage(state: state) - -state.opacity = 1.0 -state.bounds = updatedBounds -``` + ## Opacity and Updates `GroundImageState` exposes `opacity`, so you can fade an image in or out without replacing the overlay: -```swift -state.opacity = 0.35 -``` + When the underlying image changes, update `state.image`. When the geographic extent changes, update `state.bounds`. @@ -67,14 +39,6 @@ When the underlying image changes, update `state.image`. When the geographic ext Use `onClick` when the overlay should be selectable: -```swift -GroundImage( - bounds: bounds, - image: image, - onClick: { event in - print("Clicked image: \(event.state.id)") - } -) -``` + Provider support for image overlays can differ. Test the provider modules you ship with the image sizes and opacity values your app will use. diff --git a/docs/src/content/docs/components/infobubble.mdx b/docs/src/content/docs/components/infobubble.mdx index a8d31a2..91bf780 100644 --- a/docs/src/content/docs/components/infobubble.mdx +++ b/docs/src/content/docs/components/infobubble.mdx @@ -7,59 +7,27 @@ head: content: Display custom info windows anchored to markers using InfoBubble in MapConductor iOS SDK. --- +import InfoBubbleBasicExample from '~/components/components/infobubble/InfoBubbleBasicExample.astro'; +import InfoBubbleInitializer from '~/components/components/infobubble/InfoBubbleInitializer.astro'; +import InfoBubbleStylingExample from '~/components/components/infobubble/InfoBubbleStylingExample.astro'; + `InfoBubble` renders SwiftUI content anchored to a `MarkerState`. Use an info bubble when a marker needs visible, dynamic SwiftUI content. The bubble follows the marker instead of being a native provider callout. -```swift -let markerState = MarkerState( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - extra: "Tokyo Station" -) - -GoogleMapView(state: mapViewState) { - Marker(state: markerState) - - InfoBubble(marker: markerState) { - VStack(alignment: .leading, spacing: 4) { - Text(markerState.extra as? String ?? markerState.id) - .font(.headline) - Text("\(markerState.position.latitude), \(markerState.position.longitude)") - .font(.caption) - } - } -} -``` + ## Initializer -```swift -InfoBubble( - marker: MarkerState, - tailOffset: CGPoint = CGPoint(x: 0.5, y: 1.0), - useDefaultStyle: Bool = true, - style: InfoBubbleStyle = .Default, - @ViewBuilder content: () -> Content -) -``` + Use `extra` or your own model to drive the bubble content. ## Styling -The default style gives the bubble a built-in container and tail. Set `useDefaultStyle` or pass an `InfoBubbleStyle` when you need different presentation: +The default style gives the bubble a built-in container and tail. To change how it looks, pass `bubbleColor`, `borderColor`, `contentPadding`, `cornerRadius` or `tailSize` — the same parameters Android's `InfoBubble` takes. To draw everything yourself, tail included, use `InfoBubbleCustom`: -```swift -InfoBubble( - marker: markerState, - tailOffset: CGPoint(x: 0.5, y: 1.0), - useDefaultStyle: true, - style: .Default -) { - Text("Selected place") - .font(.headline) -} -``` + Because the content is SwiftUI, you can bind it to your app model, marker state, or selection state. diff --git a/docs/src/content/docs/components/mapviewcomponent.mdx b/docs/src/content/docs/components/mapviewcomponent.mdx index d78f7f0..9a2c39d 100644 --- a/docs/src/content/docs/components/mapviewcomponent.mdx +++ b/docs/src/content/docs/components/mapviewcomponent.mdx @@ -7,6 +7,10 @@ head: content: Provider-specific SwiftUI map views for Google Maps, Mapbox, MapKit, ArcGIS, and MapLibre in MapConductor iOS SDK. --- +import MapViewComponentBasicExample from '~/components/components/mapviewcomponent/MapViewComponentBasicExample.astro'; +import MapViewProviderSwitchExample from '~/components/components/mapviewcomponent/MapViewProviderSwitchExample.astro'; +import MapViewLayoutExample from '~/components/components/mapviewcomponent/MapViewLayoutExample.astro'; + The SDK exposes provider-specific SwiftUI views. Choose the view that matches the provider module used by the screen. | Provider | View state | SwiftUI view | @@ -21,44 +25,7 @@ Each view accepts a state object, optional map event handlers, and a `@MapViewCo Use a provider map view as the root container for map overlays on that screen. The map view is responsible for creating and updating the native SDK view; the content closure describes the MapConductor overlays that should be synchronized onto it. -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) - ) - - var body: some View { - GoogleMapView( - state: mapViewState, - onMapClick: { point in - print("Map clicked at \(point)") - }, - onCameraMoveEnd: { camera in - print("Camera ended at zoom \(camera.zoom)") - } - ) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(label: "T") - ) - - Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500, - fillColor: UIColor.red.withAlphaComponent(0.2) - ) - } - .ignoresSafeArea() - } -} -``` + ## Content Builder Items @@ -93,18 +60,7 @@ Pass these handlers directly when constructing the provider map view. The map view type and state type must match: -```swift -@StateObject private var state = MapboxViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) -) - -MapboxMapView(state: state) { - Marker(position: GeoPoint(latitude: 35.6812, longitude: 139.7671)) -} -``` + Overlay declarations such as `Marker`, `Circle`, and `Polyline` are shared. Provider-specific behavior belongs in provider setup, provider design types, or native SDK access through `getMapViewHolder()`. @@ -112,12 +68,6 @@ Overlay declarations such as `Marker`, `Circle`, and `Polyline` are shared. Prov Provider views are normal SwiftUI views. Apply SwiftUI layout modifiers to the provider map view itself: -```swift -GoogleMapView(state: mapViewState) { - Marker(position: tokyoStation) -} -.frame(height: 320) -.clipShape(RoundedRectangle(cornerRadius: 8)) -``` + Keep long-running data loading outside the content closure. The closure should describe current overlay state, not perform network requests or expensive coordinate processing. diff --git a/docs/src/content/docs/components/mapviewstate.mdx b/docs/src/content/docs/components/mapviewstate.mdx index bc230f4..0886cb5 100644 --- a/docs/src/content/docs/components/mapviewstate.mdx +++ b/docs/src/content/docs/components/mapviewstate.mdx @@ -7,6 +7,12 @@ head: content: Manage camera position, map type, and styling using MapViewState in MapConductor iOS SDK. --- +import MapViewStateExample from '~/components/components/mapviewstate/MapViewStateExample.astro'; +import MapViewStatePassExample from '~/components/components/mapviewstate/MapViewStatePassExample.astro'; +import MapViewStateCameraFullExample from '~/components/components/mapviewstate/MapViewStateCameraFullExample.astro'; +import MapViewStateCameraPointExample from '~/components/components/mapviewstate/MapViewStateCameraPointExample.astro'; +import MapViewStateHolderExample from '~/components/components/mapviewstate/MapViewStateHolderExample.astro'; + `MapViewState` is the base class for provider-specific state types. In application code, use one of the concrete state classes: - `GoogleMapViewState` @@ -26,45 +32,21 @@ Each state exposes: Use the concrete provider state as a SwiftUI `@StateObject` when the view owns the map state: -```swift -@StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) -) -``` + Pass the same object to the matching provider map view: -```swift -GoogleMapView(state: mapViewState) { - Marker(position: GeoPoint(latitude: 35.6812, longitude: 139.7671)) -} -``` + ## Camera Control Move to a full camera value when you need to change zoom, bearing, tilt, padding, or visible region: -```swift -mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6586, longitude: 139.7454), - zoom: 15 - ), - durationMillis: 500 -) -``` + Move to a point when the current camera settings should otherwise be preserved by the provider implementation: -```swift -mapViewState.moveCameraTo( - position: GeoPoint(latitude: 35.7101, longitude: 139.8107), - durationMillis: 500 -) -``` + To move to a bounds-like area, calculate the center and zoom in your app and call `moveCameraTo(cameraPosition:durationMillis:)`. @@ -86,9 +68,4 @@ Use it only when you need provider-specific SDK access that is not covered by th The holder may be `nil` before the native map is ready. Guard it and cast only at the point where provider-specific access is needed: -```swift -if let holder = mapViewState.getMapViewHolder(), - let nativeMapView = holder.mapView as? GMSMapView { - nativeMapView.isTrafficEnabled = true -} -``` + diff --git a/docs/src/content/docs/components/marker.mdx b/docs/src/content/docs/components/marker.mdx index 4810902..b920c4c 100644 --- a/docs/src/content/docs/components/marker.mdx +++ b/docs/src/content/docs/components/marker.mdx @@ -7,39 +7,21 @@ head: content: Add markers with custom icons, labels, dragging, and click handlers using Marker in MapConductor iOS SDK. --- +import MarkerBasicExample from '~/components/components/marker/MarkerBasicExample.astro'; +import MarkerInitializer from '~/components/components/marker/MarkerInitializer.astro'; +import MarkerInteractionExample from '~/components/components/marker/MarkerInteractionExample.astro'; +import MarkerStateExample from '~/components/components/marker/MarkerStateExample.astro'; +import MarkerForArrayExample from '~/components/components/marker/MarkerForArrayExample.astro'; + `Marker` adds a marker overlay to a map. It is a `MapOverlayItemProtocol` item used inside a provider map view content closure. Use direct initializer parameters for a small number of static markers. Use `MarkerState` when a marker moves, changes icon, is draggable, or needs to be updated from outside the map content closure. -```swift -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(label: "T"), - onClick: { state in - print("Clicked marker \(state.id)") - } -) -``` + ## Initializer -```swift -Marker( - position: GeoPoint, - id: String? = nil, - extra: Any? = nil, - icon: (any MarkerIconProtocol)? = nil, - animation: MarkerAnimation? = nil, - clickable: Bool = true, - draggable: Bool = false, - onClick: OnMarkerEventHandler? = nil, - onDragStart: OnMarkerEventHandler? = nil, - onDrag: OnMarkerEventHandler? = nil, - onDragEnd: OnMarkerEventHandler? = nil, - onAnimateStart: OnMarkerEventHandler? = nil, - onAnimateEnd: OnMarkerEventHandler? = nil -) -``` + Store app-specific data in `extra`, or encode visible text into a custom icon such as `DefaultMarkerIcon(label:)`. @@ -47,35 +29,11 @@ Store app-specific data in `extra`, or encode visible text into a custom icon su Set `clickable` to control tap handling and `draggable` to allow drag events. The event handlers receive the current `MarkerState`, so you can read the marker id, position, icon, and extra data from the state. -```swift -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - id: "tokyo-station", - extra: "Tokyo Station", - draggable: true, - onClick: { marker in - print("Clicked \(marker.extra ?? marker.id)") - }, - onDragEnd: { marker in - print("New position: \(marker.position)") - } -) -``` + ## State-Based Usage -```swift -let markerState = MarkerState( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - extra: "Tokyo Station", - icon: DefaultMarkerIcon(fillColor: .red, label: "T") -) - -Marker(state: markerState) - -markerState.position = GeoPoint(latitude: 35.6895, longitude: 139.6917) -markerState.animate(.Bounce) -``` + ## Marker Animation @@ -90,22 +48,7 @@ Use `markerState.animate(_:)` to request an animation. For a dynamic collection, create stable marker states and render them with `ForArray`: -```swift -let markers: [MarkerState] = places.map { place in - MarkerState( - position: place.coordinate, - id: place.id, - extra: place.name, - icon: DefaultMarkerIcon(label: place.shortLabel) - ) -} - -GoogleMapView(state: mapViewState) { - ForArray(markers) { marker in - Marker(state: marker) - } -} -``` + Stable ids are important for large sets because they let the SDK update existing markers instead of recreating them. diff --git a/docs/src/content/docs/components/polygon.mdx b/docs/src/content/docs/components/polygon.mdx index 7ea4ae3..9eb6d69 100644 --- a/docs/src/content/docs/components/polygon.mdx +++ b/docs/src/content/docs/components/polygon.mdx @@ -7,53 +7,25 @@ head: content: Draw polygon overlays with fill, stroke, and holes using Polygon in MapConductor iOS SDK. --- +import PolygonBasicExample from '~/components/components/polygon/PolygonBasicExample.astro'; +import PolygonInitializer from '~/components/components/polygon/PolygonInitializer.astro'; +import PolygonStateWithHolesExample from '~/components/components/polygon/PolygonStateWithHolesExample.astro'; +import PolygonHolesExample from '~/components/components/polygon/PolygonHolesExample.astro'; +import PolygonClickExample from '~/components/components/polygon/PolygonClickExample.astro'; + `Polygon` draws a filled shape. Use polygons for areas such as administrative boundaries, parcels, geofences, or custom regions. The first and last points do not need to be repeated; the provider implementation closes the shape. -```swift -Polygon( - points: [ - GeoPoint(latitude: 35.67, longitude: 139.75), - GeoPoint(latitude: 35.68, longitude: 139.76), - GeoPoint(latitude: 35.66, longitude: 139.77) - ], - strokeColor: .blue, - strokeWidth: 2, - fillColor: UIColor.blue.withAlphaComponent(0.2), - onClick: { event in - print("Clicked at \(event.clicked)") - } -) -``` + ## Initializers -```swift -Polygon( - points: [GeoPointProtocol], - id: String? = nil, - strokeColor: UIColor = .black, - strokeWidth: Double = 1.0, - fillColor: UIColor = .clear, - geodesic: Bool = false, - zIndex: Int = 0, - extra: Any? = nil, - onClick: OnPolygonEventHandler? = nil -) -``` + `PolygonState` also supports holes: -```swift -let state = PolygonState( - points: outerRing, - fillColor: UIColor.green.withAlphaComponent(0.25), - holes: [innerRing] -) - -Polygon(state: state) -``` + Use alpha on `fillColor` or `strokeColor` for transparency. @@ -61,39 +33,10 @@ Use alpha on `fillColor` or `strokeColor` for transparency. Each hole is an array of coordinates inside the outer polygon: -```swift -let outerRing = [ - GeoPoint(latitude: 35.67, longitude: 139.74), - GeoPoint(latitude: 35.69, longitude: 139.74), - GeoPoint(latitude: 35.69, longitude: 139.77), - GeoPoint(latitude: 35.67, longitude: 139.77), -] - -let innerRing = [ - GeoPoint(latitude: 35.675, longitude: 139.75), - GeoPoint(latitude: 35.685, longitude: 139.75), - GeoPoint(latitude: 35.685, longitude: 139.76), - GeoPoint(latitude: 35.675, longitude: 139.76), -] - -Polygon( - state: PolygonState( - points: outerRing, - fillColor: UIColor.green.withAlphaComponent(0.25), - holes: [innerRing] - ) -) -``` + ## Interaction Use `onClick` for selectable areas: -```swift -Polygon( - points: districtBoundary, - onClick: { event in - print("Polygon clicked at \(event.clicked)") - } -) -``` + diff --git a/docs/src/content/docs/components/polyline.mdx b/docs/src/content/docs/components/polyline.mdx index 9d977db..28b509e 100644 --- a/docs/src/content/docs/components/polyline.mdx +++ b/docs/src/content/docs/components/polyline.mdx @@ -7,76 +7,34 @@ head: content: Draw polylines with custom stroke and geodesic rendering using Polyline in MapConductor iOS SDK. --- +import PolylineBasicExample from '~/components/components/polyline/PolylineBasicExample.astro'; +import PolylineInitializerPoints from '~/components/components/polyline/PolylineInitializerPoints.astro'; +import PolylineInitializerBounds from '~/components/components/polyline/PolylineInitializerBounds.astro'; +import PolylineStateExample from '~/components/components/polyline/PolylineStateExample.astro'; +import PolylineBoundsExample from '~/components/components/polyline/PolylineBoundsExample.astro'; +import PolylineClickExample from '~/components/components/polyline/PolylineClickExample.astro'; + `Polyline` draws a line through a list of geographic points. Use polylines for routes, traces, boundaries, and any line data where the shape should follow an ordered list of coordinates. -```swift -let route: [GeoPoint] = [ - GeoPoint(latitude: 35.6812, longitude: 139.7671), - GeoPoint(latitude: 35.6586, longitude: 139.7454) -] - -Polyline( - points: route, - strokeColor: .blue, - strokeWidth: 4, - geodesic: false, - onClick: { event in - print("Clicked at \(event.clicked)") - } -) -``` + ## Initializers -```swift -Polyline( - points: [GeoPointProtocol], - id: String? = nil, - strokeColor: UIColor = .black, - strokeWidth: Double = 1.0, - geodesic: Bool = false, - extra: Any? = nil, - onClick: OnPolylineEventHandler? = nil -) -``` - -```swift -Polyline( - bounds: GeoRectBounds, - id: String? = nil, - strokeColor: UIColor = .black, - strokeWidth: Double = 1.0, - geodesic: Bool = false, - extra: Any? = nil, - onClick: OnPolylineEventHandler? = nil -) -``` - -## Updating State + -```swift -let state = PolylineState(points: route, strokeColor: .blue, strokeWidth: 3) + -Polyline(state: state) +## Updating State -state.points.append(GeoPoint(latitude: 35.7101, longitude: 139.8107)) -state.strokeColor = .red -``` + ## Bounds Outline The second initializer draws the perimeter of a `GeoRectBounds`: -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) - -Polyline(bounds: bounds, strokeColor: .red, strokeWidth: 2) -``` + This is useful for debugging geographic extents or showing a rectangular selection. It does not move the camera by itself. @@ -84,13 +42,4 @@ This is useful for debugging geographic extents or showing a rectangular selecti Use `onClick` when the user should be able to select a route or inspect a line: -```swift -Polyline( - points: route, - strokeColor: .blue, - strokeWidth: 4, - onClick: { event in - print("Polyline clicked at \(event.clicked)") - } -) -``` + diff --git a/docs/src/content/docs/core/geopoint.mdx b/docs/src/content/docs/core/geopoint.mdx index 3f6f149..4da030e 100644 --- a/docs/src/content/docs/core/geopoint.mdx +++ b/docs/src/content/docs/core/geopoint.mdx @@ -7,11 +7,17 @@ head: content: GeoPoint — geographic coordinate type used across all overlays and camera positions in MapConductor iOS SDK. --- +import GeoPointBasicExample from '~/components/core/geopoint/GeoPointBasicExample.astro'; +import GeoPointNormalizeExample from '~/components/core/geopoint/GeoPointNormalizeExample.astro'; +import GeoPointCoordinateOrderExample from '~/components/core/geopoint/GeoPointCoordinateOrderExample.astro'; +import GeoPointFromLatLongExample from '~/components/core/geopoint/GeoPointFromLatLongExample.astro'; +import GeoPointValidationExample from '~/components/core/geopoint/GeoPointValidationExample.astro'; +import GeoPointSphericalExample from '~/components/core/geopoint/GeoPointSphericalExample.astro'; +import GeoPointUrlValueExample from '~/components/core/geopoint/GeoPointUrlValueExample.astro'; + `GeoPoint` is the SDK's latitude / longitude / altitude value type. -```swift -let point = GeoPoint(latitude: 35.6812, longitude: 139.7671) -``` + ## Public API @@ -31,53 +37,30 @@ let point = GeoPoint(latitude: 35.6812, longitude: 139.7671) Use `GeoPoint` anywhere MapConductor asks for a geographic coordinate, including camera positions, marker positions, shape points, heatmap points, and click event coordinates. -```swift -let raw = GeoPoint(latitude: 95, longitude: 190) -let normalized = raw.normalize() -let valid = normalized.isValid() -let urlValue = normalized.toUrlValue() -``` + ## Coordinate Order The main initializer uses latitude first and longitude second: -```swift -let tokyo = GeoPoint(latitude: 35.6812, longitude: 139.7671) -``` + Use `fromLatLong(latitude:longitude:)` when adapting code that explicitly names latitude and longitude. Use `fromLongLat(longitude:latitude:)` when adapting data sources that store coordinates in longitude-first order, such as many GeoJSON-like formats. -```swift -let fromLatLong = GeoPoint.fromLatLong(latitude: 35.6812, longitude: 139.7671) -let fromLongLat = GeoPoint.fromLongLat(longitude: 139.7671, latitude: 35.6812) -``` + ## Validation and Wrapping `isValid()` checks whether latitude and longitude are in valid geographic ranges. `normalize()` and `wrap()` are useful when user input or calculations produce coordinates outside the usual longitude range. -```swift -let input = GeoPoint(latitude: 35.6812, longitude: 181.5) - -if input.isValid() { - Marker(position: input) -} else { - Marker(position: input.normalize()) -} -``` + Distance and heading helpers are on `Spherical`, not on `GeoPoint` itself: -```swift -let distanceMeters = Spherical.computeDistanceBetween(from: pointA, to: pointB) -let headingDegrees = Spherical.computeHeading(from: pointA, to: pointB) -``` + ## URL Values Use `toUrlValue(precision:)` when building URLs, cache keys, or debug output that needs a compact coordinate string: -```swift -let value = tokyo.toUrlValue(precision: 5) -``` + diff --git a/docs/src/content/docs/core/georectbounds.mdx b/docs/src/content/docs/core/georectbounds.mdx index 09cc8f4..84901ac 100644 --- a/docs/src/content/docs/core/georectbounds.mdx +++ b/docs/src/content/docs/core/georectbounds.mdx @@ -7,14 +7,15 @@ head: content: GeoRectBounds — define geographic bounding rectangles for overlays and camera bounds in MapConductor iOS SDK. --- +import GeoRectBoundsBasicExample from '~/components/core/georectbounds/GeoRectBoundsBasicExample.astro'; +import GeoRectBoundsFromPointsExample from '~/components/core/georectbounds/GeoRectBoundsFromPointsExample.astro'; +import GeoRectBoundsFitBoundsExample from '~/components/core/georectbounds/GeoRectBoundsFitBoundsExample.astro'; +import GeoRectBoundsContainmentExample from '~/components/core/georectbounds/GeoRectBoundsContainmentExample.astro'; +import GeoRectBoundsPolylineExample from '~/components/core/georectbounds/GeoRectBoundsPolylineExample.astro'; + `GeoRectBounds` stores optional south-west and north-east corners and can be extended with points. -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) -``` + ## Public API @@ -34,46 +35,24 @@ Use `southWest`, `northEast`, `toSpan()`, and `isEmpty` for bounds inspection. ## Building Bounds from Points -```swift -let bounds = GeoRectBounds() -for point in points { - bounds.extend(point: point) -} + + +To move the camera so the bounds are visible, use `fitBounds(bounds:padding:)`: -if let center = bounds.center { - mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition(position: center, zoom: 12), - durationMillis: 300 - ) -} -``` + -To move the map to show a bounds-like area, calculate the desired camera yourself and call `moveCameraTo(cameraPosition:durationMillis:)`. +For more control over the resulting zoom level, calculate the desired camera yourself and call `moveCameraTo(cameraPosition:durationMillis:)`. ## Containment and Intersection Bounds are useful for filtering visible data, detecting overlap between regions, and building debug overlays. -```swift -if bounds.contains(point: candidate) { - visiblePoints.append(candidate) -} - -if searchArea.intersects(other: loadedTileBounds) { - loadMoreData() -} -``` + ## Displaying Bounds To display a rectangular bounds outline, pass the bounds to `Polyline`: -```swift -Polyline( - bounds: bounds, - strokeColor: .red, - strokeWidth: 2 -) -``` + Use `expandedByDegrees(latPad:lonPad:)` when you need a simple degree-based margin around an existing bounds. This is not a replacement for camera fitting; it only returns a larger geographic rectangle. diff --git a/docs/src/content/docs/core/mapcameraposition.mdx b/docs/src/content/docs/core/mapcameraposition.mdx index 3dafb4c..29cdc2e 100644 --- a/docs/src/content/docs/core/mapcameraposition.mdx +++ b/docs/src/content/docs/core/mapcameraposition.mdx @@ -7,29 +7,20 @@ head: content: MapCameraPosition — configure map center, zoom, tilt, and bearing in MapConductor iOS SDK. --- +import MapCameraPositionBasicExample from '~/components/core/mapcameraposition/MapCameraPositionBasicExample.astro'; +import MapCameraPositionInitializerExample from '~/components/core/mapcameraposition/MapCameraPositionInitializerExample.astro'; +import MoveCameraExample from '~/components/core/mapcameraposition/MoveCameraExample.astro'; +import CopyCameraExample from '~/components/core/mapcameraposition/CopyCameraExample.astro'; +import FitBoundsExample from '~/components/core/mapcameraposition/FitBoundsExample.astro'; +import CameraEventsExample from '~/components/core/mapcameraposition/CameraEventsExample.astro'; + `MapCameraPosition` describes the map camera. -```swift -let camera = MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13, - bearing: 0, - tilt: 0 -) -``` + ## Initializer -```swift -MapCameraPosition( - position: GeoPointProtocol, - zoom: Double = 0.0, - bearing: Double = 0.0, - tilt: Double = 0.0, - paddings: MapPaddingsProtocol? = MapPaddings.Zeros, - visibleRegion: VisibleRegion? = nil -) -``` + The property is named `position`, not `target`. @@ -46,47 +37,24 @@ Provider SDKs may interpret zoom, bearing, tilt, and padding with provider-speci ## Moving the Camera -```swift -mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6586, longitude: 139.7454), - zoom: 15 - ), - durationMillis: 500 -) - -mapViewState.moveCameraTo( - position: GeoPoint(latitude: 35.7101, longitude: 139.8107), - durationMillis: 500 -) -``` + Use `copy(...)` to derive a new value from the current camera: -```swift -let next = mapViewState.cameraPosition.copy(zoom: 16) -mapViewState.moveCameraTo(cameraPosition: next, durationMillis: 300) -``` + + +## Fitting Bounds + +Use `fitBounds(bounds:padding:)` to move and zoom the camera so that a `GeoRectBounds` is fully visible: + + + +The `padding` parameter adds screen-space inset in points on all sides. Each provider calculates the resulting zoom level natively, so the exact zoom may differ slightly between providers. ## Responding to Camera Events Provider map views can report camera changes through initializer parameters: -```swift -GoogleMapView( - state: mapViewState, - onCameraMoveStart: { camera in - print("Started at \(camera.position)") - }, - onCameraMove: { camera in - print("Moving at zoom \(camera.zoom)") - }, - onCameraMoveEnd: { camera in - print("Ended at \(camera.position)") - } -) { - Marker(position: mapViewState.cameraPosition.position) -} -``` + Use camera events for UI updates, visible data loading, or analytics. Avoid heavy work in `onCameraMove`; prefer `onCameraMoveEnd` for expensive updates. diff --git a/docs/src/content/docs/core/marker-icons.mdx b/docs/src/content/docs/core/marker-icons.mdx index a976912..19221f2 100644 --- a/docs/src/content/docs/core/marker-icons.mdx +++ b/docs/src/content/docs/core/marker-icons.mdx @@ -7,6 +7,11 @@ head: content: Marker icon types including DefaultMarkerIcon and custom icon interfaces in MapConductor iOS SDK. --- +import DefaultMarkerIconExample from '~/components/core/marker-icons/DefaultMarkerIconExample.astro'; +import ImageIconExample from '~/components/core/marker-icons/ImageIconExample.astro'; +import ImageDefaultIconExample from '~/components/core/marker-icons/ImageDefaultIconExample.astro'; +import BitmapIconExample from '~/components/core/marker-icons/BitmapIconExample.astro'; + Marker icons conform to `MarkerIconProtocol`. Use marker icons to control the visual representation of `Marker` and `MarkerState`. The icon object is stored on the marker state and synchronized to the native provider marker. @@ -15,18 +20,7 @@ Use marker icons to control the visual representation of `Marker` and `MarkerSta `DefaultMarkerIcon` draws the SDK's default pin. It is the easiest way to get consistent marker visuals across providers. -```swift -let icon = DefaultMarkerIcon( - fillColor: .red, - strokeColor: .white, - label: "A" -) - -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: icon -) -``` + Available initializer parameters include `fillColor`, `strokeColor`, `strokeWidth`, `scale`, `label`, `labelTextColor`, `labelTextSize`, `labelTypeFace`, `labelStrokeColor`, `infoAnchor`, `iconSize`, and `debug`. @@ -36,37 +30,19 @@ Use `label` for short text such as a one-letter category, sequence number, or co `ImageIcon` uses a `UIImage` directly. Use it when your app already has an image asset for the marker. -```swift -let icon = ImageIcon( - image: UIImage(named: "custom-marker")!, - iconSize: 48, - anchor: CGPoint(x: 0.5, y: 1.0) -) -``` + ## ImageDefaultIcon `ImageDefaultIcon` clips an image into the default pin shape. -```swift -let icon = ImageDefaultIcon( - backgroundImage: UIImage(named: "avatar")!, - strokeColor: .white, - label: "1" -) -``` + ## BitmapIcon `BitmapIcon` wraps a rendered `UIImage`. Use it when you render a SwiftUI view or other custom drawing into an image before passing it to the marker system. -```swift -let icon = BitmapIcon( - bitmap: image, - anchor: CGPoint(x: 0.5, y: 1.0), - scale: 1.0 -) -``` + ## Choosing an Icon Type diff --git a/docs/src/content/docs/core/spherical-utilities.mdx b/docs/src/content/docs/core/spherical-utilities.mdx index 0e63015..7f8be3b 100644 --- a/docs/src/content/docs/core/spherical-utilities.mdx +++ b/docs/src/content/docs/core/spherical-utilities.mdx @@ -7,6 +7,12 @@ head: content: Spherical geometry helpers for computing distances, headings, and areas between coordinates in MapConductor iOS SDK. --- +import SphericalBasicExample from '~/components/core/spherical-utilities/SphericalBasicExample.astro'; +import SphericalLengthAreaExample from '~/components/core/spherical-utilities/SphericalLengthAreaExample.astro'; +import SphericalRouteLengthExample from '~/components/core/spherical-utilities/SphericalRouteLengthExample.astro'; +import SphericalAreaExample from '~/components/core/spherical-utilities/SphericalAreaExample.astro'; +import SphericalInterpolateExample from '~/components/core/spherical-utilities/SphericalInterpolateExample.astro'; + Spherical geometry helpers are exposed through the `Spherical` enum. Use these helpers for measurements and derived coordinates when your app needs map-related geometry outside the provider SDK. ## Public Functions @@ -18,30 +24,18 @@ Spherical geometry helpers are exposed through the `Spherical` enum. Use these h - `computeLength(_:) -> Double` - `computeArea(_:) -> Double` - `computeSignedArea(_:) -> Double` -- `sphericalInterpolate(from:to:fraction:) -> GeoPoint` +- `interpolate(from:to:fraction:) -> GeoPoint` - `linearInterpolate(from:to:fraction:) -> GeoPoint` -```swift -let tokyo = GeoPoint(latitude: 35.6812, longitude: 139.7671) -let tower = GeoPoint(latitude: 35.6586, longitude: 139.7454) - -let distance = Spherical.computeDistanceBetween(from: tokyo, to: tower) -let heading = Spherical.computeHeading(from: tokyo, to: tower) -let point = Spherical.computeOffset(origin: tokyo, distance: 1_000, heading: 90) -``` + -```swift -let routeLength = Spherical.computeLength(route) -let polygonArea = Spherical.computeArea(polygon) -``` + ## Route Length Use `computeLength(_:)` for a route or track: -```swift -let routeLengthMeters = Spherical.computeLength(routePoints) -``` + The result is in meters. @@ -49,19 +43,10 @@ The result is in meters. Use `computeArea(_:)` for the unsigned area of a closed path and `computeSignedArea(_:)` when winding direction matters: -```swift -let areaSquareMeters = Spherical.computeArea(polygonPoints) -let signedArea = Spherical.computeSignedArea(polygonPoints) -``` + ## Interpolation -Use `sphericalInterpolate(from:to:fraction:)` for movement along the sphere and `linearInterpolate(from:to:fraction:)` for simple latitude/longitude interpolation. +Use `interpolate(from:to:fraction:)` for movement along the sphere and `linearInterpolate(from:to:fraction:)` for simple latitude/longitude interpolation. -```swift -let halfway = Spherical.sphericalInterpolate( - from: pointA, - to: pointB, - fraction: 0.5 -) -``` + diff --git a/docs/src/content/docs/core/zoom-levels.mdx b/docs/src/content/docs/core/zoom-levels.mdx index 3bc7b6c..d39d901 100644 --- a/docs/src/content/docs/core/zoom-levels.mdx +++ b/docs/src/content/docs/core/zoom-levels.mdx @@ -7,45 +7,20 @@ head: content: Zoom level reference — how MapConductor iOS SDK maps zoom values to real-world scales. --- +import ZoomLevelBasicExample from '~/components/core/zoom-levels/ZoomLevelBasicExample.astro'; +import ZoomControlsExample from '~/components/core/zoom-levels/ZoomControlsExample.astro'; + Zoom is represented by `MapCameraPosition.zoom`. Zoom values are passed through to the selected provider SDK. The same numeric value generally means "closer" or "farther" in the expected way, but exact map scale and provider limits are provider-specific. -```swift -let camera = MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 14 -) - -mapViewState.moveCameraTo(cameraPosition: camera, durationMillis: 300) -``` + If an app requires zoom limits, clamp the value before moving the camera: ## Zoom Controls -```swift -let minZoom = 3.0 -let maxZoom = 18.0 - -func zoomIn() { - let current = mapViewState.cameraPosition - let nextZoom = min(maxZoom, current.zoom + 1) - mapViewState.moveCameraTo( - cameraPosition: current.copy(zoom: nextZoom), - durationMillis: 200 - ) -} - -func zoomOut() { - let current = mapViewState.cameraPosition - let nextZoom = max(minZoom, current.zoom - 1) - mapViewState.moveCameraTo( - cameraPosition: current.copy(zoom: nextZoom), - durationMillis: 200 - ) -} -``` + Use `onCameraMove` or `onCameraMoveEnd` initializer arguments on the map view to observe zoom changes. diff --git a/docs/src/content/docs/es-419/components/circle.mdx b/docs/src/content/docs/es-419/components/circle.mdx index 5d5bce6..4e0bf8f 100644 --- a/docs/src/content/docs/es-419/components/circle.mdx +++ b/docs/src/content/docs/es-419/components/circle.mdx @@ -7,40 +7,20 @@ head: content: Dibuja superposiciones circulares con radio, relleno y borde usando Circle en MapConductor iOS SDK. --- +import CircleBasicExample from '~/components/components/circle/CircleBasicExample.astro'; +import CircleInitializer from '~/components/components/circle/CircleInitializer.astro'; +import CircleClickExample from '~/components/components/circle/CircleClickExample.astro'; +import CircleStateExample from '~/components/components/circle/CircleStateExample.astro'; + `Circle` dibuja una superposición circular. Usa círculos para áreas basadas en radio, como zonas de servicio, rangos de búsqueda o geocercas. El radio se expresa en metros mediante `radiusMeters`. -```swift -Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 1_000, - strokeColor: .red, - strokeWidth: 2, - fillColor: UIColor.red.withAlphaComponent(0.2), - onClick: { event in - print("Clicked at \(event.clicked)") - } -) -``` + ## Inicializador -```swift -Circle( - center: GeoPointProtocol, - radiusMeters: Double, - geodesic: Bool = true, - clickable: Bool = true, - strokeColor: UIColor = .red, - strokeWidth: Double = 1.0, - fillColor: UIColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5), - id: String? = nil, - zIndex: Int? = nil, - extra: Any? = nil, - onClick: OnCircleEventHandler? = nil -) -``` + Usa el componente alpha de `fillColor` o `strokeColor` para la transparencia. @@ -48,29 +28,10 @@ Usa el componente alpha de `fillColor` o `strokeColor` para la transparencia. Establece `clickable` y `onClick` cuando el usuario deba poder seleccionar o inspeccionar el círculo. El evento contiene la coordenada donde se hizo clic: -```swift -Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 750, - clickable: true, - onClick: { event in - print("Circle clicked at \(event.clicked)") - } -) -``` + ## Actualización de Estado -```swift -let state = CircleState( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500 -) - -Circle(state: state) - -state.radiusMeters = 1_000 -state.fillColor = UIColor.blue.withAlphaComponent(0.2) -``` + Usa un objeto de estado cuando el radio, el centro, los colores, el z-index o el manejador de clics cambian después de que se crea la superposición. diff --git a/docs/src/content/docs/es-419/components/groundimage.mdx b/docs/src/content/docs/es-419/components/groundimage.mdx index 337821e..ad5a4e1 100644 --- a/docs/src/content/docs/es-419/components/groundimage.mdx +++ b/docs/src/content/docs/es-419/components/groundimage.mdx @@ -7,59 +7,31 @@ head: content: Superpone imágenes georeferenciadas en el mapa usando GroundImage en MapConductor iOS SDK. --- +import GroundImageBasicExample from '~/components/components/groundimage/GroundImageBasicExample.astro'; +import GroundImageInitializer from '~/components/components/groundimage/GroundImageInitializer.astro'; +import GroundImageStateExample from '~/components/components/groundimage/GroundImageStateExample.astro'; +import GroundImageOpacityExample from '~/components/components/groundimage/GroundImageOpacityExample.astro'; +import GroundImageClickExample from '~/components/components/groundimage/GroundImageClickExample.astro'; + `GroundImage` coloca un `UIImage` sobre un rectángulo geográfico. Usa imágenes en el suelo para superposiciones geográficas fijas, como planos de planta, mapas históricos, diagramas o anotaciones raster personalizadas. La imagen se posiciona mediante `GeoRectBounds`. -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) - -GroundImage( - bounds: bounds, - image: UIImage(named: "overlay")!, - opacity: 0.7, - tileSize: 512, - onClick: { event in - print("Ground image clicked: \(String(describing: event.clicked))") - } -) -``` + ## Inicializador -```swift -GroundImage( - bounds: GeoRectBounds, - image: UIImage, - opacity: Double = 1.0, - tileSize: Int = 512, - id: String? = nil, - extra: Any? = nil, - onClick: OnGroundImageEventHandler? = nil -) -``` + ## Actualización de Estado -```swift -let state = GroundImageState(bounds: bounds, image: image, opacity: 0.5) - -GroundImage(state: state) - -state.opacity = 1.0 -state.bounds = updatedBounds -``` + ## Opacidad y Actualizaciones `GroundImageState` expone `opacity`, para que puedas hacer que una imagen aparezca o desaparezca gradualmente sin reemplazar la superposición: -```swift -state.opacity = 0.35 -``` + Cuando la imagen subyacente cambia, actualiza `state.image`. Cuando la extensión geográfica cambia, actualiza `state.bounds`. @@ -67,14 +39,6 @@ Cuando la imagen subyacente cambia, actualiza `state.image`. Cuando la extensió Usa `onClick` cuando la superposición deba ser seleccionable: -```swift -GroundImage( - bounds: bounds, - image: image, - onClick: { event in - print("Clicked image: \(event.state.id)") - } -) -``` + El soporte del proveedor para superposiciones de imágenes puede variar. Prueba los módulos de proveedor que utilices con los tamaños de imagen y valores de opacidad que tu aplicación vaya a usar. diff --git a/docs/src/content/docs/es-419/components/infobubble.mdx b/docs/src/content/docs/es-419/components/infobubble.mdx index d62306b..d52b171 100644 --- a/docs/src/content/docs/es-419/components/infobubble.mdx +++ b/docs/src/content/docs/es-419/components/infobubble.mdx @@ -7,59 +7,27 @@ head: content: Muestra ventanas de información ancladas a marcadores usando InfoBubble en MapConductor iOS SDK. --- +import InfoBubbleBasicExample from '~/components/components/infobubble/InfoBubbleBasicExample.astro'; +import InfoBubbleInitializer from '~/components/components/infobubble/InfoBubbleInitializer.astro'; +import InfoBubbleStylingExample from '~/components/components/infobubble/InfoBubbleStylingExample.astro'; + `InfoBubble` renderiza contenido SwiftUI anclado a un `MarkerState`. Usa una burbuja de información cuando un marcador necesite contenido SwiftUI visible y dinámico. La burbuja sigue al marcador en lugar de ser un callout nativo del proveedor. -```swift -let markerState = MarkerState( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - extra: "Tokyo Station" -) - -GoogleMapView(state: mapViewState) { - Marker(state: markerState) - - InfoBubble(marker: markerState) { - VStack(alignment: .leading, spacing: 4) { - Text(markerState.extra as? String ?? markerState.id) - .font(.headline) - Text("\(markerState.position.latitude), \(markerState.position.longitude)") - .font(.caption) - } - } -} -``` + ## Inicializador -```swift -InfoBubble( - marker: MarkerState, - tailOffset: CGPoint = CGPoint(x: 0.5, y: 1.0), - useDefaultStyle: Bool = true, - style: InfoBubbleStyle = .Default, - @ViewBuilder content: () -> Content -) -``` + Usa `extra` o tu propio modelo para controlar el contenido de la burbuja. ## Estilo -El estilo predeterminado le da a la burbuja un contenedor y una cola integrados. Establece `useDefaultStyle` o pasa un `InfoBubbleStyle` cuando necesites una presentación diferente: +El estilo predeterminado le da a la burbuja un contenedor y una cola integrados. Para cambiar su apariencia, pasa `bubbleColor`, `borderColor`, `contentPadding`, `cornerRadius` o `tailSize` — los mismos parámetros que toma el `InfoBubble` de Android. Para dibujarlo todo tú mismo, cola incluida, usa `InfoBubbleCustom`: -```swift -InfoBubble( - marker: markerState, - tailOffset: CGPoint(x: 0.5, y: 1.0), - useDefaultStyle: true, - style: .Default -) { - Text("Selected place") - .font(.headline) -} -``` + Como el contenido es SwiftUI, puedes vincularlo a tu modelo de aplicación, al estado del marcador o al estado de selección. diff --git a/docs/src/content/docs/es-419/components/mapviewcomponent.mdx b/docs/src/content/docs/es-419/components/mapviewcomponent.mdx index 88a902c..eb5fb34 100644 --- a/docs/src/content/docs/es-419/components/mapviewcomponent.mdx +++ b/docs/src/content/docs/es-419/components/mapviewcomponent.mdx @@ -7,6 +7,10 @@ head: content: Vistas de mapa SwiftUI por proveedor — Google Maps, Mapbox, MapKit, ArcGIS y MapLibre en MapConductor iOS SDK. --- +import MapViewComponentBasicExample from '~/components/components/mapviewcomponent/MapViewComponentBasicExample.astro'; +import MapViewProviderSwitchExample from '~/components/components/mapviewcomponent/MapViewProviderSwitchExample.astro'; +import MapViewLayoutExample from '~/components/components/mapviewcomponent/MapViewLayoutExample.astro'; + El SDK expone vistas SwiftUI específicas por proveedor. Elige la vista que corresponda al módulo de proveedor utilizado por la pantalla. | Proveedor | Estado de vista | Vista SwiftUI | @@ -21,44 +25,7 @@ Cada vista acepta un objeto de estado, manejadores opcionales de eventos del map Usa una vista de mapa de proveedor como contenedor raíz para las superposiciones del mapa en esa pantalla. La vista de mapa es responsable de crear y actualizar la vista nativa del SDK; el closure de contenido describe las superposiciones de MapConductor que deben sincronizarse en ella. -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) - ) - - var body: some View { - GoogleMapView( - state: mapViewState, - onMapClick: { point in - print("Map clicked at \(point)") - }, - onCameraMoveEnd: { camera in - print("Camera ended at zoom \(camera.zoom)") - } - ) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(label: "T") - ) - - Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500, - fillColor: UIColor.red.withAlphaComponent(0.2) - ) - } - .ignoresSafeArea() - } -} -``` + ## Elementos del Content Builder @@ -93,18 +60,7 @@ Pasa estos manejadores directamente al construir la vista de mapa del proveedor. El tipo de vista de mapa y el tipo de estado deben coincidir: -```swift -@StateObject private var state = MapboxViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) -) - -MapboxMapView(state: state) { - Marker(position: GeoPoint(latitude: 35.6812, longitude: 139.7671)) -} -``` + Las declaraciones de superposiciones como `Marker`, `Circle` y `Polyline` son compartidas. El comportamiento específico del proveedor pertenece a la configuración del proveedor, los tipos de diseño del proveedor, o el acceso al SDK nativo a través de `getMapViewHolder()`. @@ -112,12 +68,6 @@ Las declaraciones de superposiciones como `Marker`, `Circle` y `Polyline` son co Las vistas de proveedor son vistas SwiftUI normales. Aplica los modificadores de layout de SwiftUI a la vista de mapa del proveedor directamente: -```swift -GoogleMapView(state: mapViewState) { - Marker(position: tokyoStation) -} -.frame(height: 320) -.clipShape(RoundedRectangle(cornerRadius: 8)) -``` + Mantén la carga de datos de larga duración fuera del closure de contenido. El closure debe describir el estado actual de las superposiciones, no realizar solicitudes de red ni procesamiento costoso de coordenadas. diff --git a/docs/src/content/docs/es-419/components/mapviewstate.mdx b/docs/src/content/docs/es-419/components/mapviewstate.mdx index 27d43bd..3e0069d 100644 --- a/docs/src/content/docs/es-419/components/mapviewstate.mdx +++ b/docs/src/content/docs/es-419/components/mapviewstate.mdx @@ -7,6 +7,12 @@ head: content: Gestiona posición de cámara, tipo de mapa y estilo usando MapViewState en MapConductor iOS SDK. --- +import MapViewStateExample from '~/components/components/mapviewstate/MapViewStateExample.astro'; +import MapViewStatePassExample from '~/components/components/mapviewstate/MapViewStatePassExample.astro'; +import MapViewStateCameraFullExample from '~/components/components/mapviewstate/MapViewStateCameraFullExample.astro'; +import MapViewStateCameraPointExample from '~/components/components/mapviewstate/MapViewStateCameraPointExample.astro'; +import MapViewStateHolderExample from '~/components/components/mapviewstate/MapViewStateHolderExample.astro'; + `MapViewState` es la clase base para los tipos de estado específicos de cada proveedor. En el código de la aplicación, usa una de las clases de estado concretas: - `GoogleMapViewState` @@ -26,45 +32,21 @@ Cada estado expone: Usa el estado de proveedor concreto como `@StateObject` de SwiftUI cuando la vista es dueña del estado del mapa: -```swift -@StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) -) -``` + Pasa el mismo objeto a la vista de mapa del proveedor correspondiente: -```swift -GoogleMapView(state: mapViewState) { - Marker(position: GeoPoint(latitude: 35.6812, longitude: 139.7671)) -} -``` + ## Control de Cámara Mueve a un valor completo de cámara cuando necesites cambiar el zoom, bearing, inclinación, padding o región visible: -```swift -mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6586, longitude: 139.7454), - zoom: 15 - ), - durationMillis: 500 -) -``` + Mueve a un punto cuando la configuración actual de la cámara debe preservarse por la implementación del proveedor: -```swift -mapViewState.moveCameraTo( - position: GeoPoint(latitude: 35.7101, longitude: 139.8107), - durationMillis: 500 -) -``` + Para moverse a un área delimitada, calcula el centro y el zoom en tu aplicación y llama a `moveCameraTo(cameraPosition:durationMillis:)`. @@ -86,9 +68,4 @@ Para moverse a un área delimitada, calcula el centro y el zoom en tu aplicació El holder puede ser `nil` antes de que el mapa nativo esté listo. Guárdalo y realiza el cast solo en el punto donde se necesite el acceso específico del proveedor: -```swift -if let holder = mapViewState.getMapViewHolder(), - let nativeMapView = holder.mapView as? GMSMapView { - nativeMapView.isTrafficEnabled = true -} -``` + diff --git a/docs/src/content/docs/es-419/components/marker.mdx b/docs/src/content/docs/es-419/components/marker.mdx index 4f8cbab..3daae5c 100644 --- a/docs/src/content/docs/es-419/components/marker.mdx +++ b/docs/src/content/docs/es-419/components/marker.mdx @@ -7,39 +7,21 @@ head: content: Agrega marcadores con iconos personalizados, arrastre y eventos de clic usando Marker en MapConductor iOS SDK. --- +import MarkerBasicExample from '~/components/components/marker/MarkerBasicExample.astro'; +import MarkerInitializer from '~/components/components/marker/MarkerInitializer.astro'; +import MarkerInteractionExample from '~/components/components/marker/MarkerInteractionExample.astro'; +import MarkerStateExample from '~/components/components/marker/MarkerStateExample.astro'; +import MarkerForArrayExample from '~/components/components/marker/MarkerForArrayExample.astro'; + `Marker` agrega una superposición de marcador a un mapa. Es un elemento `MapOverlayItemProtocol` que se usa dentro del closure de contenido de una vista de mapa de proveedor. Usa los parámetros directos del inicializador para una pequeña cantidad de marcadores estáticos. Usa `MarkerState` cuando un marcador se mueve, cambia de ícono, es arrastrable o necesita actualizarse desde fuera del closure de contenido del mapa. -```swift -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(label: "T"), - onClick: { state in - print("Clicked marker \(state.id)") - } -) -``` + ## Inicializador -```swift -Marker( - position: GeoPoint, - id: String? = nil, - extra: Any? = nil, - icon: (any MarkerIconProtocol)? = nil, - animation: MarkerAnimation? = nil, - clickable: Bool = true, - draggable: Bool = false, - onClick: OnMarkerEventHandler? = nil, - onDragStart: OnMarkerEventHandler? = nil, - onDrag: OnMarkerEventHandler? = nil, - onDragEnd: OnMarkerEventHandler? = nil, - onAnimateStart: OnMarkerEventHandler? = nil, - onAnimateEnd: OnMarkerEventHandler? = nil -) -``` + Almacena datos específicos de la aplicación en `extra`, o codifica texto visible en un ícono personalizado como `DefaultMarkerIcon(label:)`. @@ -47,35 +29,11 @@ Almacena datos específicos de la aplicación en `extra`, o codifica texto visib Establece `clickable` para controlar el manejo de toques y `draggable` para permitir eventos de arrastre. Los manejadores de eventos reciben el `MarkerState` actual, por lo que puedes leer el id del marcador, la posición, el ícono y los datos extra desde el estado. -```swift -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - id: "tokyo-station", - extra: "Tokyo Station", - draggable: true, - onClick: { marker in - print("Clicked \(marker.extra ?? marker.id)") - }, - onDragEnd: { marker in - print("New position: \(marker.position)") - } -) -``` + ## Uso Basado en Estado -```swift -let markerState = MarkerState( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - extra: "Tokyo Station", - icon: DefaultMarkerIcon(fillColor: .red, label: "T") -) - -Marker(state: markerState) - -markerState.position = GeoPoint(latitude: 35.6895, longitude: 139.6917) -markerState.animate(.Bounce) -``` + ## Animación de Marcador @@ -90,22 +48,7 @@ Usa `markerState.animate(_:)` para solicitar una animación. Para una colección dinámica, crea estados de marcador estables y renderízalos con `ForArray`: -```swift -let markers: [MarkerState] = places.map { place in - MarkerState( - position: place.coordinate, - id: place.id, - extra: place.name, - icon: DefaultMarkerIcon(label: place.shortLabel) - ) -} - -GoogleMapView(state: mapViewState) { - ForArray(markers) { marker in - Marker(state: marker) - } -} -``` + Los ids estables son importantes para conjuntos grandes porque permiten que el SDK actualice los marcadores existentes en lugar de recrearlos. diff --git a/docs/src/content/docs/es-419/components/polygon.mdx b/docs/src/content/docs/es-419/components/polygon.mdx index 0b2ca72..5713539 100644 --- a/docs/src/content/docs/es-419/components/polygon.mdx +++ b/docs/src/content/docs/es-419/components/polygon.mdx @@ -7,53 +7,25 @@ head: content: Dibuja polígonos con relleno, borde y agujeros usando Polygon en MapConductor iOS SDK. --- +import PolygonBasicExample from '~/components/components/polygon/PolygonBasicExample.astro'; +import PolygonInitializer from '~/components/components/polygon/PolygonInitializer.astro'; +import PolygonStateWithHolesExample from '~/components/components/polygon/PolygonStateWithHolesExample.astro'; +import PolygonHolesExample from '~/components/components/polygon/PolygonHolesExample.astro'; +import PolygonClickExample from '~/components/components/polygon/PolygonClickExample.astro'; + `Polygon` dibuja una forma rellena. Usa polígonos para áreas como límites administrativos, parcelas, geocercas o regiones personalizadas. El primer y último punto no necesitan repetirse; la implementación del proveedor cierra la forma. -```swift -Polygon( - points: [ - GeoPoint(latitude: 35.67, longitude: 139.75), - GeoPoint(latitude: 35.68, longitude: 139.76), - GeoPoint(latitude: 35.66, longitude: 139.77) - ], - strokeColor: .blue, - strokeWidth: 2, - fillColor: UIColor.blue.withAlphaComponent(0.2), - onClick: { event in - print("Clicked at \(event.clicked)") - } -) -``` + ## Inicializadores -```swift -Polygon( - points: [GeoPointProtocol], - id: String? = nil, - strokeColor: UIColor = .black, - strokeWidth: Double = 1.0, - fillColor: UIColor = .clear, - geodesic: Bool = false, - zIndex: Int = 0, - extra: Any? = nil, - onClick: OnPolygonEventHandler? = nil -) -``` + `PolygonState` también admite agujeros: -```swift -let state = PolygonState( - points: outerRing, - fillColor: UIColor.green.withAlphaComponent(0.25), - holes: [innerRing] -) - -Polygon(state: state) -``` + Usa alpha en `fillColor` o `strokeColor` para la transparencia. @@ -61,39 +33,10 @@ Usa alpha en `fillColor` o `strokeColor` para la transparencia. Cada agujero es un arreglo de coordenadas dentro del polígono exterior: -```swift -let outerRing = [ - GeoPoint(latitude: 35.67, longitude: 139.74), - GeoPoint(latitude: 35.69, longitude: 139.74), - GeoPoint(latitude: 35.69, longitude: 139.77), - GeoPoint(latitude: 35.67, longitude: 139.77), -] - -let innerRing = [ - GeoPoint(latitude: 35.675, longitude: 139.75), - GeoPoint(latitude: 35.685, longitude: 139.75), - GeoPoint(latitude: 35.685, longitude: 139.76), - GeoPoint(latitude: 35.675, longitude: 139.76), -] - -Polygon( - state: PolygonState( - points: outerRing, - fillColor: UIColor.green.withAlphaComponent(0.25), - holes: [innerRing] - ) -) -``` + ## Interacción Usa `onClick` para áreas seleccionables: -```swift -Polygon( - points: districtBoundary, - onClick: { event in - print("Polygon clicked at \(event.clicked)") - } -) -``` + diff --git a/docs/src/content/docs/es-419/components/polyline.mdx b/docs/src/content/docs/es-419/components/polyline.mdx index 386d846..1a258e9 100644 --- a/docs/src/content/docs/es-419/components/polyline.mdx +++ b/docs/src/content/docs/es-419/components/polyline.mdx @@ -7,76 +7,34 @@ head: content: Dibuja polilíneas con trazo personalizado y renderizado geodésico usando Polyline en MapConductor iOS SDK. --- +import PolylineBasicExample from '~/components/components/polyline/PolylineBasicExample.astro'; +import PolylineInitializerPoints from '~/components/components/polyline/PolylineInitializerPoints.astro'; +import PolylineInitializerBounds from '~/components/components/polyline/PolylineInitializerBounds.astro'; +import PolylineStateExample from '~/components/components/polyline/PolylineStateExample.astro'; +import PolylineBoundsExample from '~/components/components/polyline/PolylineBoundsExample.astro'; +import PolylineClickExample from '~/components/components/polyline/PolylineClickExample.astro'; + `Polyline` dibuja una línea a través de una lista de puntos geográficos. Usa polilíneas para rutas, trazos, límites y cualquier dato de línea donde la forma deba seguir una lista ordenada de coordenadas. -```swift -let route: [GeoPoint] = [ - GeoPoint(latitude: 35.6812, longitude: 139.7671), - GeoPoint(latitude: 35.6586, longitude: 139.7454) -] - -Polyline( - points: route, - strokeColor: .blue, - strokeWidth: 4, - geodesic: false, - onClick: { event in - print("Clicked at \(event.clicked)") - } -) -``` + ## Inicializadores -```swift -Polyline( - points: [GeoPointProtocol], - id: String? = nil, - strokeColor: UIColor = .black, - strokeWidth: Double = 1.0, - geodesic: Bool = false, - extra: Any? = nil, - onClick: OnPolylineEventHandler? = nil -) -``` - -```swift -Polyline( - bounds: GeoRectBounds, - id: String? = nil, - strokeColor: UIColor = .black, - strokeWidth: Double = 1.0, - geodesic: Bool = false, - extra: Any? = nil, - onClick: OnPolylineEventHandler? = nil -) -``` - -## Actualización de Estado + -```swift -let state = PolylineState(points: route, strokeColor: .blue, strokeWidth: 3) + -Polyline(state: state) +## Actualización de Estado -state.points.append(GeoPoint(latitude: 35.7101, longitude: 139.8107)) -state.strokeColor = .red -``` + ## Contorno de Bounds El segundo inicializador dibuja el perímetro de un `GeoRectBounds`: -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) - -Polyline(bounds: bounds, strokeColor: .red, strokeWidth: 2) -``` + Esto es útil para depurar extensiones geográficas o mostrar una selección rectangular. No mueve la cámara por sí solo. @@ -84,13 +42,4 @@ Esto es útil para depurar extensiones geográficas o mostrar una selección rec Usa `onClick` cuando el usuario deba poder seleccionar una ruta o inspeccionar una línea: -```swift -Polyline( - points: route, - strokeColor: .blue, - strokeWidth: 4, - onClick: { event in - print("Polyline clicked at \(event.clicked)") - } -) -``` + diff --git a/docs/src/content/docs/es-419/core/geopoint.mdx b/docs/src/content/docs/es-419/core/geopoint.mdx index b781c05..fb56200 100644 --- a/docs/src/content/docs/es-419/core/geopoint.mdx +++ b/docs/src/content/docs/es-419/core/geopoint.mdx @@ -7,11 +7,17 @@ head: content: GeoPoint — tipo de coordenada geográfica usado en todas las superposiciones y posiciones de cámara en MapConductor iOS SDK. --- +import GeoPointBasicExample from '~/components/core/geopoint/GeoPointBasicExample.astro'; +import GeoPointNormalizeExample from '~/components/core/geopoint/GeoPointNormalizeExample.astro'; +import GeoPointCoordinateOrderExample from '~/components/core/geopoint/GeoPointCoordinateOrderExample.astro'; +import GeoPointFromLatLongExample from '~/components/core/geopoint/GeoPointFromLatLongExample.astro'; +import GeoPointValidationExample from '~/components/core/geopoint/GeoPointValidationExample.astro'; +import GeoPointSphericalExample from '~/components/core/geopoint/GeoPointSphericalExample.astro'; +import GeoPointUrlValueExample from '~/components/core/geopoint/GeoPointUrlValueExample.astro'; + `GeoPoint` es el tipo de valor de latitud / longitud / altitud del SDK. -```swift -let point = GeoPoint(latitude: 35.6812, longitude: 139.7671) -``` + ## API pública @@ -31,53 +37,30 @@ let point = GeoPoint(latitude: 35.6812, longitude: 139.7671) Usa `GeoPoint` en cualquier lugar donde MapConductor solicite una coordenada geográfica, incluyendo posiciones de cámara, posiciones de marcadores, puntos de figuras, puntos de mapa de calor y coordenadas de eventos de clic. -```swift -let raw = GeoPoint(latitude: 95, longitude: 190) -let normalized = raw.normalize() -let valid = normalized.isValid() -let urlValue = normalized.toUrlValue() -``` + ## Orden de coordenadas El inicializador principal usa latitud primero y longitud segundo: -```swift -let tokyo = GeoPoint(latitude: 35.6812, longitude: 139.7671) -``` + Usa `fromLatLong(latitude:longitude:)` al adaptar código que nombra explícitamente latitud y longitud. Usa `fromLongLat(longitude:latitude:)` al adaptar fuentes de datos que almacenan coordenadas en orden longitud-primero, como muchos formatos similares a GeoJSON. -```swift -let fromLatLong = GeoPoint.fromLatLong(latitude: 35.6812, longitude: 139.7671) -let fromLongLat = GeoPoint.fromLongLat(longitude: 139.7671, latitude: 35.6812) -``` + ## Validación y envoltura `isValid()` verifica si la latitud y longitud están dentro de los rangos geográficos válidos. `normalize()` y `wrap()` son útiles cuando la entrada del usuario o los cálculos producen coordenadas fuera del rango habitual de longitud. -```swift -let input = GeoPoint(latitude: 35.6812, longitude: 181.5) - -if input.isValid() { - Marker(position: input) -} else { - Marker(position: input.normalize()) -} -``` + Los helpers de distancia y dirección están en `Spherical`, no en `GeoPoint`: -```swift -let distanceMeters = Spherical.computeDistanceBetween(from: pointA, to: pointB) -let headingDegrees = Spherical.computeHeading(from: pointA, to: pointB) -``` + ## Valores de URL Usa `toUrlValue(precision:)` al construir URLs, claves de caché o salidas de depuración que necesiten una cadena de coordenada compacta: -```swift -let value = tokyo.toUrlValue(precision: 5) -``` + diff --git a/docs/src/content/docs/es-419/core/georectbounds.mdx b/docs/src/content/docs/es-419/core/georectbounds.mdx index 94c5026..0a8a35c 100644 --- a/docs/src/content/docs/es-419/core/georectbounds.mdx +++ b/docs/src/content/docs/es-419/core/georectbounds.mdx @@ -7,14 +7,15 @@ head: content: GeoRectBounds — define rectángulos geográficos para superposiciones y límites de cámara en MapConductor iOS SDK. --- +import GeoRectBoundsBasicExample from '~/components/core/georectbounds/GeoRectBoundsBasicExample.astro'; +import GeoRectBoundsFromPointsExample from '~/components/core/georectbounds/GeoRectBoundsFromPointsExample.astro'; +import GeoRectBoundsFitBoundsExample from '~/components/core/georectbounds/GeoRectBoundsFitBoundsExample.astro'; +import GeoRectBoundsContainmentExample from '~/components/core/georectbounds/GeoRectBoundsContainmentExample.astro'; +import GeoRectBoundsPolylineExample from '~/components/core/georectbounds/GeoRectBoundsPolylineExample.astro'; + `GeoRectBounds` almacena las esquinas opcionales suroeste y noreste, y puede extenderse con puntos. -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) -``` + ## API pública @@ -34,46 +35,24 @@ Usa `southWest`, `northEast`, `toSpan()` e `isEmpty` para inspeccionar los lími ## Construir límites desde puntos -```swift -let bounds = GeoRectBounds() -for point in points { - bounds.extend(point: point) -} + + +Para mover la cámara de modo que los límites sean visibles, usa `fitBounds(bounds:padding:)`: -if let center = bounds.center { - mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition(position: center, zoom: 12), - durationMillis: 300 - ) -} -``` + -Para mover el mapa y mostrar un área con límites, calcula la posición de cámara deseada y llama a `moveCameraTo(cameraPosition:durationMillis:)`. +Para mayor control sobre el nivel de zoom resultante, calcula la posición de cámara deseada y llama a `moveCameraTo(cameraPosition:durationMillis:)`. ## Contenido e intersección Los límites son útiles para filtrar datos visibles, detectar superposición entre regiones y construir superposiciones de depuración. -```swift -if bounds.contains(point: candidate) { - visiblePoints.append(candidate) -} - -if searchArea.intersects(other: loadedTileBounds) { - loadMoreData() -} -``` + ## Mostrar límites Para mostrar el contorno de un límite rectangular, pasa los límites a `Polyline`: -```swift -Polyline( - bounds: bounds, - strokeColor: .red, - strokeWidth: 2 -) -``` + Usa `expandedByDegrees(latPad:lonPad:)` cuando necesites un margen simple en grados alrededor de unos límites existentes. Esto no reemplaza el ajuste de cámara; solo devuelve un rectángulo geográfico más grande. diff --git a/docs/src/content/docs/es-419/core/mapcameraposition.mdx b/docs/src/content/docs/es-419/core/mapcameraposition.mdx index 5eb563b..707cb19 100644 --- a/docs/src/content/docs/es-419/core/mapcameraposition.mdx +++ b/docs/src/content/docs/es-419/core/mapcameraposition.mdx @@ -7,29 +7,20 @@ head: content: MapCameraPosition — configura centro, zoom, inclinación y orientación del mapa en MapConductor iOS SDK. --- +import MapCameraPositionBasicExample from '~/components/core/mapcameraposition/MapCameraPositionBasicExample.astro'; +import MapCameraPositionInitializerExample from '~/components/core/mapcameraposition/MapCameraPositionInitializerExample.astro'; +import MoveCameraExample from '~/components/core/mapcameraposition/MoveCameraExample.astro'; +import CopyCameraExample from '~/components/core/mapcameraposition/CopyCameraExample.astro'; +import FitBoundsExample from '~/components/core/mapcameraposition/FitBoundsExample.astro'; +import CameraEventsExample from '~/components/core/mapcameraposition/CameraEventsExample.astro'; + `MapCameraPosition` describe la cámara del mapa. -```swift -let camera = MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13, - bearing: 0, - tilt: 0 -) -``` + ## Inicializador -```swift -MapCameraPosition( - position: GeoPointProtocol, - zoom: Double = 0.0, - bearing: Double = 0.0, - tilt: Double = 0.0, - paddings: MapPaddingsProtocol? = MapPaddings.Zeros, - visibleRegion: VisibleRegion? = nil -) -``` + La propiedad se llama `position`, no `target`. @@ -46,47 +37,24 @@ Los SDK de proveedores pueden interpretar el acercamiento, la dirección, la inc ## Mover la cámara -```swift -mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6586, longitude: 139.7454), - zoom: 15 - ), - durationMillis: 500 -) - -mapViewState.moveCameraTo( - position: GeoPoint(latitude: 35.7101, longitude: 139.8107), - durationMillis: 500 -) -``` + Usa `copy(...)` para derivar un nuevo valor desde la cámara actual: -```swift -let next = mapViewState.cameraPosition.copy(zoom: 16) -mapViewState.moveCameraTo(cameraPosition: next, durationMillis: 300) -``` + + +## Ajustar a límites + +Usa `fitBounds(bounds:padding:)` para mover y hacer zoom de la cámara de modo que un `GeoRectBounds` sea completamente visible: + + + +El parámetro `padding` agrega un margen en puntos en todos los lados del espacio de pantalla. Cada proveedor calcula el nivel de zoom resultante de forma nativa, por lo que el zoom exacto puede diferir ligeramente entre proveedores. ## Responder a eventos de cámara Las vistas de mapa de los proveedores pueden reportar cambios de cámara a través de parámetros del inicializador: -```swift -GoogleMapView( - state: mapViewState, - onCameraMoveStart: { camera in - print("Started at \(camera.position)") - }, - onCameraMove: { camera in - print("Moving at zoom \(camera.zoom)") - }, - onCameraMoveEnd: { camera in - print("Ended at \(camera.position)") - } -) { - Marker(position: mapViewState.cameraPosition.position) -} -``` + Usa los eventos de cámara para actualizar la interfaz, cargar datos visibles o analíticas. Evita trabajo pesado en `onCameraMove`; prefiere `onCameraMoveEnd` para actualizaciones costosas. diff --git a/docs/src/content/docs/es-419/core/marker-icons.mdx b/docs/src/content/docs/es-419/core/marker-icons.mdx index 7e941f1..eb16aef 100644 --- a/docs/src/content/docs/es-419/core/marker-icons.mdx +++ b/docs/src/content/docs/es-419/core/marker-icons.mdx @@ -7,6 +7,11 @@ head: content: Tipos de íconos de marcador, incluidos DefaultMarkerIcon e interfaces personalizadas en MapConductor iOS SDK. --- +import DefaultMarkerIconExample from '~/components/core/marker-icons/DefaultMarkerIconExample.astro'; +import ImageIconExample from '~/components/core/marker-icons/ImageIconExample.astro'; +import ImageDefaultIconExample from '~/components/core/marker-icons/ImageDefaultIconExample.astro'; +import BitmapIconExample from '~/components/core/marker-icons/BitmapIconExample.astro'; + Los iconos de marcador conforman el protocolo `MarkerIconProtocol`. Usa iconos de marcador para controlar la representación visual de `Marker` y `MarkerState`. El objeto de icono se almacena en el estado del marcador y se sincroniza con el marcador nativo del proveedor. @@ -15,18 +20,7 @@ Usa iconos de marcador para controlar la representación visual de `Marker` y `M `DefaultMarkerIcon` dibuja el pin predeterminado del SDK. Es la forma más sencilla de obtener una apariencia de marcador consistente entre proveedores. -```swift -let icon = DefaultMarkerIcon( - fillColor: .red, - strokeColor: .white, - label: "A" -) - -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: icon -) -``` + Los parámetros disponibles del inicializador incluyen `fillColor`, `strokeColor`, `strokeWidth`, `scale`, `label`, `labelTextColor`, `labelTextSize`, `labelTypeFace`, `labelStrokeColor`, `infoAnchor`, `iconSize` y `debug`. @@ -36,37 +30,19 @@ Usa `label` para texto corto, como una letra de categoría, número de secuencia `ImageIcon` usa un `UIImage` directamente. Úsalo cuando tu app ya tenga un recurso de imagen para el marcador. -```swift -let icon = ImageIcon( - image: UIImage(named: "custom-marker")!, - iconSize: 48, - anchor: CGPoint(x: 0.5, y: 1.0) -) -``` + ## ImageDefaultIcon `ImageDefaultIcon` recorta una imagen en la forma del pin predeterminado. -```swift -let icon = ImageDefaultIcon( - backgroundImage: UIImage(named: "avatar")!, - strokeColor: .white, - label: "1" -) -``` + ## BitmapIcon `BitmapIcon` envuelve un `UIImage` renderizado. Úsalo cuando renderices una vista SwiftUI u otro dibujo personalizado en una imagen antes de pasarlo al sistema de marcadores. -```swift -let icon = BitmapIcon( - bitmap: image, - anchor: CGPoint(x: 0.5, y: 1.0), - scale: 1.0 -) -``` + ## Elegir un tipo de icono diff --git a/docs/src/content/docs/es-419/core/spherical-utilities.mdx b/docs/src/content/docs/es-419/core/spherical-utilities.mdx index af7f571..e4b8f25 100644 --- a/docs/src/content/docs/es-419/core/spherical-utilities.mdx +++ b/docs/src/content/docs/es-419/core/spherical-utilities.mdx @@ -7,6 +7,12 @@ head: content: Utilidades de geometría esférica para calcular distancias, rumbos y áreas en MapConductor iOS SDK. --- +import SphericalBasicExample from '~/components/core/spherical-utilities/SphericalBasicExample.astro'; +import SphericalLengthAreaExample from '~/components/core/spherical-utilities/SphericalLengthAreaExample.astro'; +import SphericalRouteLengthExample from '~/components/core/spherical-utilities/SphericalRouteLengthExample.astro'; +import SphericalAreaExample from '~/components/core/spherical-utilities/SphericalAreaExample.astro'; +import SphericalInterpolateExample from '~/components/core/spherical-utilities/SphericalInterpolateExample.astro'; + Los helpers de geometría esférica se exponen a través del enum `Spherical`. Usa estos helpers para mediciones y coordenadas derivadas cuando tu app necesite geometría relacionada con mapas fuera del SDK del proveedor. ## Funciones públicas @@ -18,30 +24,18 @@ Los helpers de geometría esférica se exponen a través del enum `Spherical`. U - `computeLength(_:) -> Double` - `computeArea(_:) -> Double` - `computeSignedArea(_:) -> Double` -- `sphericalInterpolate(from:to:fraction:) -> GeoPoint` +- `interpolate(from:to:fraction:) -> GeoPoint` - `linearInterpolate(from:to:fraction:) -> GeoPoint` -```swift -let tokyo = GeoPoint(latitude: 35.6812, longitude: 139.7671) -let tower = GeoPoint(latitude: 35.6586, longitude: 139.7454) - -let distance = Spherical.computeDistanceBetween(from: tokyo, to: tower) -let heading = Spherical.computeHeading(from: tokyo, to: tower) -let point = Spherical.computeOffset(origin: tokyo, distance: 1_000, heading: 90) -``` + -```swift -let routeLength = Spherical.computeLength(route) -let polygonArea = Spherical.computeArea(polygon) -``` + ## Longitud de ruta Usa `computeLength(_:)` para una ruta o trayectoria: -```swift -let routeLengthMeters = Spherical.computeLength(routePoints) -``` + El resultado está en metros. @@ -49,19 +43,10 @@ El resultado está en metros. Usa `computeArea(_:)` para el área sin signo de un camino cerrado y `computeSignedArea(_:)` cuando la dirección de enrollamiento sea importante: -```swift -let areaSquareMeters = Spherical.computeArea(polygonPoints) -let signedArea = Spherical.computeSignedArea(polygonPoints) -``` + ## Interpolación -Usa `sphericalInterpolate(from:to:fraction:)` para el movimiento a lo largo de la esfera y `linearInterpolate(from:to:fraction:)` para la interpolación simple de latitud/longitud. +Usa `interpolate(from:to:fraction:)` para el movimiento a lo largo de la esfera y `linearInterpolate(from:to:fraction:)` para la interpolación simple de latitud/longitud. -```swift -let halfway = Spherical.sphericalInterpolate( - from: pointA, - to: pointB, - fraction: 0.5 -) -``` + diff --git a/docs/src/content/docs/es-419/core/zoom-levels.mdx b/docs/src/content/docs/es-419/core/zoom-levels.mdx index 44a1499..624b66d 100644 --- a/docs/src/content/docs/es-419/core/zoom-levels.mdx +++ b/docs/src/content/docs/es-419/core/zoom-levels.mdx @@ -7,45 +7,20 @@ head: content: Referencia de niveles de zoom — cómo MapConductor iOS SDK mapea los valores de zoom a escalas reales. --- +import ZoomLevelBasicExample from '~/components/core/zoom-levels/ZoomLevelBasicExample.astro'; +import ZoomControlsExample from '~/components/core/zoom-levels/ZoomControlsExample.astro'; + El acercamiento se representa mediante `MapCameraPosition.zoom`. Los valores de acercamiento se pasan directamente al SDK del proveedor seleccionado. El mismo valor numérico generalmente significa "más cerca" o "más lejos" de la manera esperada, pero la escala exacta del mapa y los límites del proveedor son específicos de cada proveedor. -```swift -let camera = MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 14 -) - -mapViewState.moveCameraTo(cameraPosition: camera, durationMillis: 300) -``` + Si una app requiere límites de acercamiento, ajusta el valor antes de mover la cámara: ## Controles de acercamiento -```swift -let minZoom = 3.0 -let maxZoom = 18.0 - -func zoomIn() { - let current = mapViewState.cameraPosition - let nextZoom = min(maxZoom, current.zoom + 1) - mapViewState.moveCameraTo( - cameraPosition: current.copy(zoom: nextZoom), - durationMillis: 200 - ) -} - -func zoomOut() { - let current = mapViewState.cameraPosition - let nextZoom = max(minZoom, current.zoom - 1) - mapViewState.moveCameraTo( - cameraPosition: current.copy(zoom: nextZoom), - durationMillis: 200 - ) -} -``` + Usa los argumentos del inicializador `onCameraMove` o `onCameraMoveEnd` en la vista del mapa para observar cambios de acercamiento. diff --git a/docs/src/content/docs/es-419/event/event-handlers.mdx b/docs/src/content/docs/es-419/event/event-handlers.mdx index 30b5d03..5f2be60 100644 --- a/docs/src/content/docs/es-419/event/event-handlers.mdx +++ b/docs/src/content/docs/es-419/event/event-handlers.mdx @@ -7,35 +7,13 @@ head: content: Manejadores de eventos de mapa y superposiciones, incluidos onMapClick, onMarkerClick y onDragEnd en MapConductor iOS SDK. --- +import EventHandlersMapExample from '~/components/event/EventHandlersMapExample.astro'; +import EventHandlersMarkerExample from '~/components/event/EventHandlersMarkerExample.astro'; +import EventHandlersShapeExample from '~/components/event/EventHandlersShapeExample.astro'; + Los manejadores de eventos del mapa se pasan a cada inicializador de vista de mapa específico del proveedor. No son modificadores de vista SwiftUI. -```swift -GoogleMapView( - state: mapViewState, - onMapLoaded: { state in - print("Loaded: \(state.id)") - }, - onMapClick: { point in - print("Clicked: \(point.latitude), \(point.longitude)") - }, - onCameraMoveStart: { camera in - print("Move started: \(camera.zoom)") - }, - onCameraMove: { camera in - print("Moving: \(camera.position)") - }, - onCameraMoveEnd: { camera in - print("Move ended: \(camera.zoom)") - } -) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - onClick: { marker in - print("Marker clicked: \(marker.id)") - } - ) -} -``` + Los mismos argumentos del inicializador están disponibles en `GoogleMapView`, `MapboxMapView`, `MapKitMapView`, `MapLibreMapView` y `ArcGISMapView`. @@ -50,45 +28,12 @@ Los mismos argumentos del inicializador están disponibles en `GoogleMapView`, ` - `onAnimateStart: (MarkerState) -> Void` - `onAnimateEnd: (MarkerState) -> Void` -```swift -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - draggable: true, - onClick: { state in - print("Clicked marker \(state.id)") - }, - onDragEnd: { state in - print("Dropped at \(state.position)") - } -) -``` + ## Eventos de figuras Los manejadores de clic de figuras se configuran en el inicializador de la superposición o del estado: -```swift -Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500, - onClick: { event in - print("Circle clicked at \(event.clicked)") - } -) - -Polyline( - points: route, - onClick: { event in - print("Polyline clicked at \(event.clicked)") - } -) - -Polygon( - points: polygon, - onClick: { event in - print("Polygon clicked at \(event.clicked)") - } -) -``` + `GroundImage` también tiene `onClick: (GroundImageEvent) -> Void`. `RasterLayerState` define un tipo `RasterLayerEvent`, pero `RasterLayer` en sí no expone un inicializador `onClick`. diff --git a/docs/src/content/docs/es-419/experimental/geojson-layer.mdx b/docs/src/content/docs/es-419/experimental/geojson-layer.mdx new file mode 100644 index 0000000..c444653 --- /dev/null +++ b/docs/src/content/docs/es-419/experimental/geojson-layer.mdx @@ -0,0 +1,65 @@ +--- +title: Capa GeoJSON +head: + - tag: meta + attrs: + name: description + content: Renderiza datos GeoJSON como una capa de mapa basada en tiles usando MapConductorGeoJSON en el iOS SDK. +--- + +import GeoJSONPackageInstall from '~/components/experimental/geojson-layer/GeoJSONPackageInstall.astro'; +import GeoJSONProductDep from '~/components/experimental/geojson-layer/GeoJSONProductDep.astro'; +import GeoJSONBasicExample from '~/components/experimental/geojson-layer/GeoJSONBasicExample.astro'; +import GeoJSONLayerStateInit from '~/components/experimental/geojson-layer/GeoJSONLayerStateInit.astro'; +import GeoJSONLayerStateUpdate from '~/components/experimental/geojson-layer/GeoJSONLayerStateUpdate.astro'; +import GeoJSONLayerStyle from '~/components/experimental/geojson-layer/GeoJSONLayerStyle.astro'; +import GeoJSONLoadFeatures from '~/components/experimental/geojson-layer/GeoJSONLoadFeatures.astro'; +import GeoJSONFeatureState from '~/components/experimental/geojson-layer/GeoJSONFeatureState.astro'; +import GeoJSONClickHandler from '~/components/experimental/geojson-layer/GeoJSONClickHandler.astro'; +import GeoJSONClickHandlerMap from '~/components/experimental/geojson-layer/GeoJSONClickHandlerMap.astro'; + +`MapConductorGeoJSON` renderiza colecciones de características GeoJSON como una capa raster basada en tiles. Funciona con todos los proveedores de mapas a través de la integración estándar de `RasterLayer`. + +## Paquete + + + +Agrega el producto a tu target: + + + +## Uso básico + + + +## GeoJSONLayerState + +`GeoJSONLayerState` administra el servidor de tiles y el pipeline de renderizado. + + + +Las propiedades pueden actualizarse en tiempo de ejecución: + + + +## LayerStyle + + + +## Cargar características + +Pasa un array `[GeoJSONFeature]` directamente a `GeoJSONLayer`. La capa vuelve a renderizar los tiles cada vez que cambia la lista de características. + + + +Para estado de características observable con visibilidad por característica: + + + +## Manejo de clics + + + +Llama a `layerState.processClick(geoPoint:)` desde el handler `onMapClick` de tu mapa para enrutar los toques a través del hit test GeoJSON: + + diff --git a/docs/src/content/docs/es-419/experimental/heatmap.mdx b/docs/src/content/docs/es-419/experimental/heatmap.mdx index 42f1e5c..d307b51 100644 --- a/docs/src/content/docs/es-419/experimental/heatmap.mdx +++ b/docs/src/content/docs/es-419/experimental/heatmap.mdx @@ -7,6 +7,13 @@ head: content: Superposición de mapa de calor en mosaico compatible con cualquier proveedor en MapConductor iOS SDK. --- +import HeatmapPackageInstall from '~/components/experimental/heatmap/HeatmapPackageInstall.astro'; +import HeatmapProductDep from '~/components/experimental/heatmap/HeatmapProductDep.astro'; +import HeatmapBasicExample from '~/components/experimental/heatmap/HeatmapBasicExample.astro'; +import HeatmapBatchPoints from '~/components/experimental/heatmap/HeatmapBatchPoints.astro'; +import HeatmapDynamicState from '~/components/experimental/heatmap/HeatmapDynamicState.astro'; +import HeatmapCustomGradient from '~/components/experimental/heatmap/HeatmapCustomGradient.astro'; + El módulo `MapConductorHeatmap` proporciona una superposición de mapa de calor independiente de la implementación del mapa. Renderiza los datos del mapa de calor como una capa raster y funciona con cualquier proveedor de mapa. Usa mapas de calor cuando cada punto tenga un peso numérico y el usuario necesite comprender la densidad o intensidad en un área en lugar de inspeccionar marcadores individuales. @@ -15,53 +22,23 @@ Usa mapas de calor cuando cada punto tenga un peso numérico y el usuario necesi Agrega a tu Package.swift: -```swift -.package(url: "https://github.com/MapConductor/ios-heatmap", from: "{HEATMAP_MODULE_VERSION}"), -``` + En las dependencias de tu target: -```swift -.product(name: "MapConductorHeatmap", package: "ios-heatmap"), -``` + ## Uso Básico Usa `HeatmapPointView` para una pequeña cantidad de puntos o cuando los puntos se declaran naturalmente como contenido SwiftUI dentro de la superposición: -```swift -import MapConductorHeatmap - -GoogleMapView(state: mapViewState) { - HeatmapOverlay( - radiusPx: 20, - opacity: 0.7 - ) { - HeatmapPointView( - position: GeoPoint(latitude: 35.6762, longitude: 139.6503), - weight: 1.0 - ) - HeatmapPointView( - position: GeoPoint(latitude: 35.6895, longitude: 139.6917), - weight: 2.5 - ) - } -} -``` + ## Puntos en Lote Para conjuntos de datos grandes, usa `HeatmapPoints` para agregar todos los puntos en una sola actualización. Esto evita muchas actualizaciones separadas de aparición/desaparición de puntos: -```swift -let pointStates: [HeatmapPointState] = [...] - -GoogleMapView(state: mapViewState) { - HeatmapOverlay(radiusPx: 20) { - HeatmapPoints(pointStates) - } -} -``` + `HeatmapPoint` es el valor de datos interno utilizado por el renderizador. En el contenido del mapa, usa `HeatmapPointView` o `HeatmapPoints`. @@ -69,51 +46,11 @@ GoogleMapView(state: mapViewState) { Usa `HeatmapOverlayState` para controlar las propiedades del mapa de calor en tiempo de ejecución: -```swift -struct DynamicHeatmapMap: View { - @StateObject private var heatmapState = HeatmapOverlayState( - radiusPx: 20, - opacity: 0.7 - ) - @StateObject private var mapViewState = GoogleMapViewState() - - var body: some View { - VStack { - GoogleMapView(state: mapViewState) { - HeatmapOverlay(state: heatmapState) { - HeatmapPoints(pointStates) - } - } - - HStack { - Text("Radius:") - Slider(value: .init( - get: { Double(heatmapState.radiusPx) }, - set: { heatmapState.radiusPx = Int($0) } - ), in: 10...50) - - Text("Opacity:") - Slider(value: $heatmapState.opacity, in: 0...1) - } - .padding() - } - } -} -``` + ## Gradiente Personalizado -```swift -let gradient = HeatmapGradient( - stops: [ - HeatmapGradientStop(position: 0.0, color: UIColor(red: 0, green: 0, blue: 1, alpha: 1)), // Azul - HeatmapGradientStop(position: 0.5, color: UIColor(red: 0, green: 1, blue: 0, alpha: 1)), // Verde - HeatmapGradientStop(position: 1.0, color: UIColor(red: 1, green: 0, blue: 0, alpha: 1)) // Rojo - ] -) - -let heatmapState = HeatmapOverlayState(gradient: gradient) -``` + `HeatmapOverlayState` también admite `maxIntensity`, `weightProvider`, `tileSize`, `trackPointUpdates` y `disableTileServerCache` a través de sus inicializadores. Úsalos cuando necesites escala de intensidad consistente, ponderación personalizada o comportamiento de caché de tiles para conjuntos de datos que cambian frecuentemente. diff --git a/docs/src/content/docs/es-419/experimental/marker-clustering.mdx b/docs/src/content/docs/es-419/experimental/marker-clustering.mdx index 7ac69e7..d906bb1 100644 --- a/docs/src/content/docs/es-419/experimental/marker-clustering.mdx +++ b/docs/src/content/docs/es-419/experimental/marker-clustering.mdx @@ -7,51 +7,25 @@ head: content: Agrupación automática de marcadores en clústeres en MapConductor iOS SDK. --- +import MarkerClusterPackageInstall from '~/components/experimental/marker-clustering/MarkerClusterPackageInstall.astro'; +import MarkerClusterProductDep from '~/components/experimental/marker-clustering/MarkerClusterProductDep.astro'; +import MarkerClusterBasicExample from '~/components/experimental/marker-clustering/MarkerClusterBasicExample.astro'; +import MarkerClusterOnClick from '~/components/experimental/marker-clustering/MarkerClusterOnClick.astro'; +import MarkerClusterState from '~/components/experimental/marker-clustering/MarkerClusterState.astro'; + El nombre del producto de paquete y del módulo de agrupación de marcadores es `MapConductorMarkerCluster`. Usa la agrupación de marcadores cuando una pantalla pueda mostrar muchos marcadores al mismo tiempo y los pines individuales se superpongan o sean costosos de actualizar. El módulo reemplaza grupos de marcadores de origen cercanos con marcadores de grupo basándose en la cámara y el viewport actuales. -```swift -.package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "{MARKER_CLUSTERING_MODULE_VERSION}") -``` + -```swift -.product(name: "MapConductorMarkerCluster", package: "ios-marker-cluster") -``` + ## Uso Básico `MarkerClusterGroup` es genérico sobre el tipo de marcador real del proveedor. Usa el alias de tipo del módulo del proveedor. -```swift -import MapConductorCore -import MapConductorForGoogleMaps -import MapConductorMarkerCluster - -struct ClusterMap: View { - @StateObject private var mapViewState = GoogleMapViewState() - - @State private var clusterStrategy = - MarkerClusterStrategy( - clusterRadiusPx: 60, - minClusterSize: 2, - enableZoomAnimation: true, - enablePanAnimation: true - ) - - let markers: [MarkerState] - - var body: some View { - GoogleMapView(state: mapViewState) { - MarkerClusterGroup(strategy: clusterStrategy) { - ForArray(markers) { marker in - Marker(state: marker) - } - } - } - } -} -``` + `MarkerClusterGroup` extrae valores `MarkerState` del closure de contenido, aplica la estrategia de renderizado y pasa el resto del contenido de superposición. Mantén los ids de los marcadores estables para que la agrupación pueda rastrear los marcadores de origen entre actualizaciones. @@ -83,41 +57,12 @@ Los alias de tipo de proveedor disponibles actualmente incluyen: Usa `onClusterClick` para responder cuando el usuario toque un grupo: -```swift -MarkerClusterGroup( - onClusterClick: { cluster in - print("Cluster clicked: \(cluster.count)") - } -) { - ForArray(markers) { marker in - Marker(state: marker) - } -} -``` + ## Estado del Grupo -Usa `MarkerClusterGroupState` cuando quieras configuración de agrupación observable o círculos de radio de depuración: - -```swift -@StateObject private var clusterState = - MarkerClusterGroupState( - clusterRadiusPx: 60, - minClusterSize: 2 - ) - -MarkerClusterGroup(state: clusterState) { - ForArray(markers) { marker in - Marker(state: marker) - } -} -``` - -`MarkerClusterGroupState` expone propiedades de visualización del radio de depuración: +Usa `MarkerClusterGroupState` cuando quieras configuración de agrupación observable: -- `showClusterRadiusCircle` -- `clusterRadiusStrokeColor` -- `clusterRadiusStrokeWidth` -- `clusterRadiusFillColor` + -Estas son útiles mientras ajustas `clusterRadiusPx` y `expandMargin`. +`debugHullPolygons` dibuja el casco convexo de cada clúster y es útil mientras ajustas `clusterRadiusPx` y `expandMargin`. Es el único interruptor de depuración que expone la API de agrupación, y es el mismo en Android, iOS y React. diff --git a/docs/src/content/docs/es-419/get-started.mdx b/docs/src/content/docs/es-419/get-started.mdx index d898215..3a861cb 100644 --- a/docs/src/content/docs/es-419/get-started.mdx +++ b/docs/src/content/docs/es-419/get-started.mdx @@ -7,7 +7,13 @@ head: content: Tutorial de inicio rápido para MapConductor iOS SDK — agrega un mapa y un marcador en minutos. --- -Este tutorial muestra una pequeña pantalla de mapa en SwiftUI usando las APIs disponibles en el código fuente de iOS actual. Comienza con Google Maps por concreción, luego señala los tipos de proveedor que deberías intercambiar al usar otro SDK de mapas. +import GetStartedPackagesExample from '~/components/get-started/GetStartedPackagesExample.astro'; +import GetStartedProductsExample from '~/components/get-started/GetStartedProductsExample.astro'; +import GetStartedDisplayMapExample from '~/components/get-started/GetStartedDisplayMapExample.astro'; +import GetStartedHandleInteractionsExample from '~/components/get-started/GetStartedHandleInteractionsExample.astro'; +import GetStartedMoveCameraExample from '~/components/get-started/GetStartedMoveCameraExample.astro'; + +Este tutorial muestra una pequeña pantalla de mapa en SwiftUI usando las APIs disponibles en el código fuente de iOS actual. Comienza con MapKit por concreción, ya que es un framework del sistema en iOS y no necesita clave de API ni ninguna otra configuración nativa, luego señala los tipos de proveedor que deberías intercambiar al usar otro SDK de mapas. ## Qué Vas a Construir @@ -20,92 +26,29 @@ Este tutorial muestra una pequeña pantalla de mapa en SwiftUI usando las APIs d - Xcode con soporte para Swift Package Manager - Una aplicación iOS en SwiftUI -- La configuración nativa para el proveedor que elijas, como una clave de API de Google Maps o un token de acceso de Mapbox +- La configuración nativa para el proveedor que elijas, como una clave de API de Google Maps o un token de acceso de Mapbox (no es necesaria con MapKit) ## Agregar Paquetes Agrega el paquete principal y un paquete de proveedor: -```swift -.package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), -.package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}") -``` + Luego agrega los productos a tu objetivo: -```swift -.product(name: "MapConductorCore", package: "ios-sdk-core"), -.product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps") -``` + Solo agrega los módulos de proveedor que uses. Los módulos opcionales como `MapConductorHeatmap` y `MapConductorMarkerCluster` se pueden agregar más tarde cuando una pantalla necesite esas superposiciones. -## Inicializar Google Maps - -```swift -import GoogleMaps +## No Se Necesita Configuración Nativa -@main -struct ExampleApp: App { - init() { - GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` +MapKit es un framework del sistema, por lo que no hay clave de API, token de acceso ni paso de inicialización del SDK. Puedes ir directo a mostrar un mapa. Para otros proveedores, realiza la configuración nativa del SDK requerida por ese proveedor antes de mostrar el mapa. MapConductor no reemplaza las claves de API, tokens de acceso, licencias ni la configuración de tiles del proveedor. ## Mostrar un Mapa -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) - ) - - var body: some View { - GoogleMapView( - state: mapViewState, - onMapClick: { point in - print("Map clicked at \(point)") - } - ) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(fillColor: .red, label: "T"), - onClick: { marker in - print("Marker clicked: \(marker.id)") - } - ) - - Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 1_000, - fillColor: UIColor.blue.withAlphaComponent(0.2) - ) - - Polyline(points: [ - GeoPoint(latitude: 35.6812, longitude: 139.7671), - GeoPoint(latitude: 35.6586, longitude: 139.7454) - ]) - } - .ignoresSafeArea() - } -} -``` + La vista de mapa es propietaria de la instancia de mapa nativa. El `@StateObject` es propietario del estado del mapa entre proveedores, como la posición actual de la cámara y el diseño de mapa seleccionado. @@ -115,45 +58,13 @@ El closure de contenido se construye con `MapViewContentBuilder`. Acepta element Los eventos del mapa son parámetros del inicializador en la vista de mapa del proveedor: -```swift -GoogleMapView( - state: mapViewState, - onMapLoaded: { state in - print("Loaded map \(state.id)") - }, - onMapClick: { point in - print("Tapped \(point.latitude), \(point.longitude)") - }, - onCameraMoveEnd: { camera in - print("Camera ended at zoom \(camera.zoom)") - } -) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - draggable: true, - onClick: { marker in - print("Marker clicked: \(marker.id)") - }, - onDragEnd: { marker in - print("Marker dropped at \(marker.position)") - } - ) -} -``` - -Estos no son modificadores de SwiftUI. Usa los parámetros del inicializador directamente en `GoogleMapView`, `MapboxMapView`, `MapKitMapView`, `MapLibreMapView` o `ArcGISMapView`. + + +Estos no son modificadores de SwiftUI. Usa los parámetros del inicializador directamente en `MapKitMapView`, `GoogleMapView`, `MapboxMapView`, `MapLibreMapView` o `ArcGISMapView`. ## Mover la Cámara -```swift -mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6586, longitude: 139.7454), - zoom: 15 - ), - durationMillis: 500 -) -``` + Si tu aplicación necesita enmarcar un conjunto de puntos, calcula el centro y el zoom de la cámara en tu propio código y luego llama a `moveCameraTo(cameraPosition:durationMillis:)`. diff --git a/docs/src/content/docs/es-419/index.mdx b/docs/src/content/docs/es-419/index.mdx index 77918ba..7e5e9ba 100644 --- a/docs/src/content/docs/es-419/index.mdx +++ b/docs/src/content/docs/es-419/index.mdx @@ -14,6 +14,18 @@ import { CardGrid, Card } from "@astrojs/starlight/components"; import Features from "~/components/index/Features.astro"; import MapConductorIntroduction from "~/components/index/MapConductorIntroduction.astro"; import TopCards from "~/components/index/TopCards.astro"; +import { Image } from "astro:assets"; +import topBanner from "~/assets/top-page/es-419-comic-why-map-conductor.jpg"; + + + +MapConductor — Un SDK de mapas unificado para Android que ofrece una API común para múltiples proveedores de mapas -# Acerca de MapConductor - - ## Funcionalidades compatibles ## Paquetes de Proveedores diff --git a/docs/src/content/docs/es-419/mapviewholder/arcgis-2d.mdx b/docs/src/content/docs/es-419/mapviewholder/arcgis-2d.mdx new file mode 100644 index 0000000..c0781ec --- /dev/null +++ b/docs/src/content/docs/es-419/mapviewholder/arcgis-2d.mdx @@ -0,0 +1,60 @@ +--- +title: ArcGISMapView2D (vista plana 2D) +head: + - tag: meta + attrs: + name: description + content: ArcGISMapView2D — vista de mapa plana 2D de ArcGIS en MapConductor iOS SDK. +--- +import ArcGIS2DBasicUsage from '~/components/mapviewholder/arcgis-2d/ArcGIS2DBasicUsage.astro'; +import ArcGIS2DInitializerParams from '~/components/mapviewholder/arcgis-2d/ArcGIS2DInitializerParams.astro'; +import ArcGIS2DMapHolderExample from '~/components/mapviewholder/arcgis-2d/ArcGIS2DMapHolderExample.astro'; + +`ArcGISMapView2D` es una vista SwiftUI que muestra un mapa plano 2D de ArcGIS. Mientras que el `ArcGISMapView` estándar se basa en un `SceneView` 3D, `ArcGISMapView2D` usa el `MapView` 2D de ArcGIS. + +## Descripción general + +Úsalo cuando necesites un mapa de ArcGIS especializado para visualización 2D. `ArcGISMapViewState` se comparte con el `ArcGISMapView` estándar — no se necesita un tipo de estado separado. + +`ArcGISMapView2D` se proporciona en el módulo `MapConductorForArcGIS`. + +## Diferencias con ArcGISMapView + +| | `ArcGISMapView` | `ArcGISMapView2D` | +|---|---|---| +| Vista nativa | 3D `SceneView` | 2D `MapView` | +| Soporte de tilt | Sí | No (siempre `0`) | +| Edificios 3D | Sí | No | +| Tipo de estado | `ArcGISMapViewState` | `ArcGISMapViewState` (compartido) | + +- Usa un `MapView` 2D en lugar de un `SceneView` 3D +- `tilt` siempre queda fijo en `0` — la perspectiva 3D no está disponible +- No renderiza edificios 3D ni elevación +- Más liviano para pantallas que no necesitan visualización 3D +- `ArcGISMapViewState` puede compartirse con `ArcGISMapView` + +## Uso básico + + + +## Parámetros del inicializador + + + +- **`state`**: `ArcGISMapViewState`. Mantiene la posición de cámara, el diseño del mapa, el controlador y otros estados. +- **`onMapLoaded`**: Se llama cuando el mapa terminó de cargar. +- **`onMapClick`**: Se llama cuando el usuario toca una parte del mapa que no es un overlay. +- **`onMapLongClick`**: Se llama cuando el usuario mantiene presionada una parte del mapa que no es un overlay. +- **`onCameraMoveStart`**: Se llama cuando inicia el movimiento de la cámara. +- **`onCameraMove`**: Se llama mientras la cámara se mueve. +- **`onCameraMoveEnd`**: Se llama cuando termina el movimiento de la cámara. +- **`sdkInitialize`**: Un closure para realizar la inicialización del SDK de ArcGIS (configuración de API key). Se llama una sola vez por proceso. +- **`content`**: Un closure `@MapViewContentBuilder` para colocar overlays del mapa como marcadores, polilíneas y polígonos. + +## Notas + +En `ArcGISMapView2D`, `tilt` siempre queda fijo en `0`. Aunque se especifique `tilt` en `MapCameraPosition`, el `MapView` 2D lo ignora y no realiza visualización 3D ni perspectiva tridimensional. + +Para cambios de basemap que pueden representarse con `ArcGISDesign`, actualiza `mapViewState.mapDesignType` en lugar de acceder directamente al SDK nativo. Para acceder al `ArcGIS.Map` nativo, castea `holder.map`: + + diff --git a/docs/src/content/docs/es-419/mapviewholder/arcgis.mdx b/docs/src/content/docs/es-419/mapviewholder/arcgis.mdx new file mode 100644 index 0000000..0c75508 --- /dev/null +++ b/docs/src/content/docs/es-419/mapviewholder/arcgis.mdx @@ -0,0 +1,20 @@ +--- +title: ArcGIS MapViewHolder +head: + - tag: meta + attrs: + name: description + content: ArcGISMapViewHolder — accede a la instancia nativa de ArcGIS en MapConductor iOS SDK. +--- +import ArcGISSceneHolderExample from '~/components/mapviewholder/arcgis/ArcGISSceneHolderExample.astro'; +import ArcGISMapHolderExample from '~/components/mapviewholder/arcgis/ArcGISMapHolderExample.astro'; + +`ArcGISMapViewState.getMapViewHolder()` devuelve `AnyMapViewHolder?`. Con `ArcGISMapView` (3D), `holder.map` puede hacerse cast a `ArcGIS.Scene`; con `ArcGISMapView2D` (2D), `holder.map` puede hacerse cast a `ArcGIS.Map`. + + + +Para acceder al `Map` nativo desde un mapa 2D: + + + +Para cambios de mapa base que puedan representarse con `ArcGISDesign`, actualiza `mapViewState.mapDesignType` en lugar de acceder directamente al SDK nativo. diff --git a/docs/src/content/docs/es-419/mapviewholder/googlemaps.mdx b/docs/src/content/docs/es-419/mapviewholder/googlemaps.mdx index ee5386e..5854063 100644 --- a/docs/src/content/docs/es-419/mapviewholder/googlemaps.mdx +++ b/docs/src/content/docs/es-419/mapviewholder/googlemaps.mdx @@ -6,16 +6,10 @@ head: name: description content: GoogleMapViewHolder — accede a la instancia nativa de Google Maps en MapConductor iOS SDK. --- +import GoogleMapsHolderExample from '~/components/mapviewholder/googlemaps/GoogleMapsHolderExample.astro'; `GoogleMapViewState.getMapViewHolder()` devuelve `AnyMapViewHolder?`. El objeto nativo de Google Maps se almacena como `Any` y puede hacerse cast a `GMSMapView`. -```swift -import GoogleMaps - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? GMSMapView { - mapView.mapType = .hybrid -} -``` + Usa `toScreenOffset(position:)` y `fromScreenOffset(offset:)` para la conversión de coordenadas sin hacer cast al tipo nativo del SDK. diff --git a/docs/src/content/docs/es-419/mapviewholder/here-maps.mdx b/docs/src/content/docs/es-419/mapviewholder/here-maps.mdx new file mode 100644 index 0000000..bfa1ace --- /dev/null +++ b/docs/src/content/docs/es-419/mapviewholder/here-maps.mdx @@ -0,0 +1,15 @@ +--- +title: HERE Maps MapViewHolder +head: + - tag: meta + attrs: + name: description + content: HereMapViewHolder — accede a la instancia nativa de HERE en MapConductor iOS SDK. +--- +import HereMapsHolderExample from '~/components/mapviewholder/here-maps/HereMapsHolderExample.astro'; + +`HereMapViewState.getMapViewHolder()` devuelve `AnyMapViewHolder?`. La vista nativa puede hacerse cast al `MapView` de HERE SDK; `holder.map` puede hacerse cast a `MapScene`. + + + +Para cambios de estilo que puedan representarse con `HereMapDesign`, actualiza `mapViewState.mapDesignType` en lugar de acceder directamente al SDK nativo. diff --git a/docs/src/content/docs/es-419/mapviewholder/index.mdx b/docs/src/content/docs/es-419/mapviewholder/index.mdx index 2fb6795..1bc9d97 100644 --- a/docs/src/content/docs/es-419/mapviewholder/index.mdx +++ b/docs/src/content/docs/es-419/mapviewholder/index.mdx @@ -6,18 +6,15 @@ head: name: description content: MapViewHolder — accede a la instancia nativa del mapa en MapConductor iOS SDK. --- +import ScreenOffsetExample from '~/components/mapviewholder/index/ScreenOffsetExample.astro'; +import CastMapViewExample from '~/components/mapviewholder/index/CastMapViewExample.astro'; +import GuardHolderExample from '~/components/mapviewholder/index/GuardHolderExample.astro'; `MapViewStateProtocol.getMapViewHolder()` devuelve `AnyMapViewHolder?`. Usa el holder como vía de escape para características nativas del SDK que no forman parte de la API común de MapConductor. No es necesario para el manejo habitual de marcadores, figuras, cámara o eventos. -```swift -if let holder = mapViewState.getMapViewHolder() { - let screenPoint = holder.toScreenOffset( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671) - ) -} -``` + `AnyMapViewHolder` expone: @@ -29,23 +26,12 @@ if let holder = mapViewState.getMapViewHolder() { Las clases de holder específicas del proveedor, como `GoogleMapViewHolder`, son detalles de implementación internos en el código fuente actual. Si necesitas acceso al SDK nativo, haz un cast de `holder.mapView` o `holder.map` al tipo del SDK nativo. -```swift -import GoogleMaps - -if let holder = mapViewState.getMapViewHolder(), - let googleMap = holder.mapView as? GMSMapView { - googleMap.mapType = .hybrid -} -``` + ## Disponibilidad El holder puede ser `nil` antes de que el mapa nativo esté listo. Accede a él desde un punto en tu interfaz donde el mapa ya haya sido creado, y siempre guarda el valor opcional: -```swift -guard let holder = mapViewState.getMapViewHolder() else { - return -} -``` + Para características portables, prefiere las APIs de MapConductor. Para comportamiento exclusivo del proveedor, mantén los casts cerca de la pantalla específica del proveedor para que el resto del código de mapa permanezca compartido. diff --git a/docs/src/content/docs/es-419/mapviewholder/mapbox.mdx b/docs/src/content/docs/es-419/mapviewholder/mapbox.mdx index ce2abe0..7c72655 100644 --- a/docs/src/content/docs/es-419/mapviewholder/mapbox.mdx +++ b/docs/src/content/docs/es-419/mapviewholder/mapbox.mdx @@ -6,19 +6,8 @@ head: name: description content: MapboxMapViewHolder — accede a la instancia nativa de Mapbox en MapConductor iOS SDK. --- +import MapboxHolderExample from '~/components/mapviewholder/mapbox/MapboxHolderExample.astro'; `MapboxViewState.getMapViewHolder()` devuelve `AnyMapViewHolder?`. La vista nativa puede hacerse cast al `MapView` de Mapbox; `holder.map` puede hacerse cast a `MapboxMap`. -```swift -import MapboxMaps - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? MapView { - mapView.mapboxMap.setCamera( - to: CameraOptions( - center: CLLocationCoordinate2D(latitude: 35.6812, longitude: 139.7671), - zoom: 14 - ) - ) -} -``` + diff --git a/docs/src/content/docs/es-419/mapviewholder/mapkit.mdx b/docs/src/content/docs/es-419/mapviewholder/mapkit.mdx index d6fb289..db63c86 100644 --- a/docs/src/content/docs/es-419/mapviewholder/mapkit.mdx +++ b/docs/src/content/docs/es-419/mapviewholder/mapkit.mdx @@ -6,20 +6,13 @@ head: name: description content: MapKitMapViewHolder — accede a la instancia nativa de MapKit en MapConductor iOS SDK. --- +import MapKitHolderExample from '~/components/mapviewholder/mapkit/MapKitHolderExample.astro'; +import MapKitDesignTypeExample from '~/components/mapviewholder/mapkit/MapKitDesignTypeExample.astro'; `MapKitViewState.getMapViewHolder()` devuelve `AnyMapViewHolder?`. El objeto nativo de MapKit puede hacerse cast a `MKMapView`. -```swift -import MapKit - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? MKMapView { - mapView.showsTraffic = true -} -``` + Los cambios de tipo de mapa que están cubiertos por el SDK deben usar `mapDesignType` en su lugar: -```swift -mapViewState.mapDesignType = MapKitMapDesign.Hybrid -``` + diff --git a/docs/src/content/docs/es-419/mapviewholder/maplibre.mdx b/docs/src/content/docs/es-419/mapviewholder/maplibre.mdx index 0130d9e..4bf7304 100644 --- a/docs/src/content/docs/es-419/mapviewholder/maplibre.mdx +++ b/docs/src/content/docs/es-419/mapviewholder/maplibre.mdx @@ -6,18 +6,8 @@ head: name: description content: MapLibreMapViewHolder — accede a la instancia nativa de MapLibre en MapConductor iOS SDK. --- +import MapLibreHolderExample from '~/components/mapviewholder/maplibre/MapLibreHolderExample.astro'; `MapLibreViewState.getMapViewHolder()` devuelve `AnyMapViewHolder?`. El objeto nativo de MapLibre puede hacerse cast a `MLNMapView`. -```swift -import MapLibre - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? MLNMapView { - mapView.setCenter( - CLLocationCoordinate2D(latitude: 35.6812, longitude: 139.7671), - zoomLevel: 14, - animated: true - ) -} -``` + diff --git a/docs/src/content/docs/es-419/modules.mdx b/docs/src/content/docs/es-419/modules.mdx index b922bec..f15043f 100644 --- a/docs/src/content/docs/es-419/modules.mdx +++ b/docs/src/content/docs/es-419/modules.mdx @@ -7,6 +7,9 @@ head: content: Descripción de los módulos del MapConductor iOS SDK — productos de Swift Package Manager para cada proveedor. --- +import ModulesPackageSwiftExample from '~/components/modules/ModulesPackageSwiftExample.astro'; +import ModulesOptionalPackagesExample from '~/components/modules/ModulesOptionalPackagesExample.astro'; + El repositorio está dividido en paquetes Swift independientes para que cada aplicación pueda depender únicamente de los proveedores y utilidades que realmente usa. Una aplicación típica depende de `MapConductorCore` más un módulo de proveedor. | Repositorio del paquete | Producto | Tipos principales | @@ -16,8 +19,11 @@ El repositorio está dividido en paquetes Swift independientes para que cada apl | `ios-for-mapbox` | `MapConductorForMapbox` | `MapboxViewState`, `MapboxMapView`, `MapboxMapDesign` | | `ios-for-mapkit` | `MapConductorForMapKit` | `MapKitViewState`, `MapKitMapView`, `MapKitMapDesign` | | `ios-for-maplibre` | `MapConductorForMapLibre` | `MapLibreViewState`, `MapLibreMapView`, `MapLibreDesign` | +| `ios-for-arcgis` | `MapConductorForArcGIS` | `ArcGISMapViewState`, `ArcGISMapView`, `ArcGISMapDesign` | +| `ios-for-here` | `MapConductorForHERE` | `HereMapViewState`, `HereMapView`, `HereMapDesign` | | `ios-heatmap` | `MapConductorHeatmap` | `HeatmapOverlay`, `HeatmapOverlayState`, `HeatmapPointView`, `HeatmapPoints` | | `ios-marker-cluster` | `MapConductorMarkerCluster` | `MarkerClusterGroup`, `MarkerClusterGroupState`, `MarkerClusterStrategy` | +| `ios-geojson-layer` | `MapConductorGeoJSON` | `GeoJSONLayerState`, `GeoJSONLayer`, `GeoJSONFeature` | ## Paquete Principal @@ -45,43 +51,12 @@ Usa los paquetes opcionales solo en las pantallas que necesiten esas funciones. - `MapConductorHeatmap` agrega renderizado de mapas de calor basado en tiles con `HeatmapOverlay`, `HeatmapPointView`, `HeatmapPoints` y `HeatmapOverlayState`. - `MapConductorMarkerCluster` agrega agrupación de marcadores con `MarkerClusterGroup`, `MarkerClusterGroupState` y `MarkerClusterStrategy`. +- `MapConductorGeoJSON` agrega renderizado de capas GeoJSON basado en tiles con `GeoJSONLayer`, `GeoJSONLayerState` y `GeoJSONFeature`. ## Ejemplo de Package.swift -```swift -dependencies: [ - .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}"), -], -targets: [ - .target( - name: "App", - dependencies: [ - .product(name: "MapConductorCore", package: "ios-sdk-core"), - .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), - ] - ) -] -``` + ## Ejemplo con Módulos Opcionales -```swift -dependencies: [ - .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-heatmap", from: "{HEATMAP_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "{MARKER_CLUSTERING_MODULE_VERSION}"), -], -targets: [ - .target( - name: "App", - dependencies: [ - .product(name: "MapConductorCore", package: "ios-sdk-core"), - .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), - .product(name: "MapConductorHeatmap", package: "ios-heatmap"), - .product(name: "MapConductorMarkerCluster", package: "ios-marker-cluster"), - ] - ) -] -``` + diff --git a/docs/src/content/docs/es-419/setup/arcgis.mdx b/docs/src/content/docs/es-419/setup/arcgis.mdx new file mode 100644 index 0000000..f15e8db --- /dev/null +++ b/docs/src/content/docs/es-419/setup/arcgis.mdx @@ -0,0 +1,123 @@ +--- +title: Configuración de ArcGIS +head: + - tag: meta + attrs: + name: description + content: Guía paso a paso para configurar ArcGIS como proveedor en MapConductor iOS SDK. +--- + +import ArcGISInitExample from '~/components/setup/arcgis/ArcGISInitExample.astro'; +import ArcGISInitSdkInitializeExample from '~/components/setup/arcgis/ArcGISInitSdkInitializeExample.astro'; +import ArcGISVerification3DExample from '~/components/setup/arcgis/ArcGISVerification3DExample.astro'; +import ArcGISVerification2DExample from '~/components/setup/arcgis/ArcGISVerification2DExample.astro'; +import ArcGISStylePresetsExample from '~/components/setup/arcgis/ArcGISStylePresetsExample.astro'; +import ArcGISCustomDesignExample from '~/components/setup/arcgis/ArcGISCustomDesignExample.astro'; + +Esta sección cubre el proceso de configuración para la integración de ArcGIS Maps SDK for Swift con MapConductor. + +Usa este proveedor cuando tu aplicación ya depende de ArcGIS Maps SDK for Swift, necesita mapas base de ArcGIS a través de `ArcGISDesign`, o necesita vistas de mapa ArcGIS en 3D y 2D. + +## Requisitos Previos + +- Entorno de desarrollo iOS (Xcode 15+) +- Cuenta de desarrollador de ArcGIS +- Clave de API de ArcGIS +- Objetivo de despliegue de iOS establecido en 17.0 o superior + +## Pasos de Configuración + +### 1. Configuración de ArcGIS + +1. Ve a [ArcGIS Location Platform](https://location.arcgis.com/) o [ArcGIS Developers](https://developers.arcgis.com/) +2. Crea una clave de API para tu proyecto o aplicación +3. Habilita los servicios de ArcGIS que necesites, como mapas base, geocodificación o rutas +4. Restringe la clave de API para el uso de tu aplicación + +### 2. Configuración de Swift Package Manager + +Agrega MapConductorForArcGIS a tu proyecto de Xcode: + +1. En Xcode, ve a **File > Add Package Dependencies** +2. Ingresa la URL del paquete: + - `https://github.com/MapConductor/ios-for-arcgis` + +ArcGIS Maps SDK for Swift se instala automáticamente como dependencia — no se requieren paquetes adicionales. + +### 3. Inicializar ArcGIS en tu Aplicación + +Llama a `arcGISApiKeyInitialize(apiKey:)` una vez antes de que se muestre cualquier vista de mapa. El lugar recomendado es el struct `@main` de tu App: + + + +Alternativamente, pasa la llamada mediante el parámetro `sdkInitialize` en `ArcGISMapView` o `ArcGISMapView2D`: + + + +## Verificación + +Compila y ejecuta tu aplicación con el siguiente código para verificar la configuración del mapa 3D: + + + +Para mostrar un mapa 2D, pasa el mismo `ArcGISMapViewState` a `ArcGISMapView2D`: + + + +Si el mapa se muestra sin errores, tu configuración está funcionando. Prueba las interacciones básicas (acercamiento, desplazamiento y tilt en la vista 3D) para confirmar que todo funciona correctamente. + +## Solución de Problemas + +### Problemas Comunes + +**El mapa no se muestra (pantalla en blanco)** + +- Verifica que la clave de API pasada a `arcGISApiKeyInitialize(apiKey:)` sea correcta +- Comprueba que `arcGISApiKeyInitialize(apiKey:)` se llame antes de que se cree cualquier vista de mapa +- Asegúrate de que los servicios de ArcGIS requeridos estén habilitados para la clave de API +- Verifica que el objetivo de despliegue de iOS sea 17.0 o superior + +**Errores de clave de API** + +- Verifica el valor de la clave de API +- Asegúrate de que la clave no haya sido revocada +- Confirma que las restricciones de la clave coincidan con el bundle ID de tu aplicación y los servicios habilitados + +**Errores de compilación** + +- Asegúrate de que `MapConductorForArcGIS` esté correctamente vinculado a tu target +- Actualiza las dependencias de Swift Package Manager +- Limpia la carpeta de compilación (Cmd+Shift+K) +- Elimina los datos derivados: `~/Library/Developer/Xcode/DerivedData` + +### Servicios de Ubicación + +Para habilitar funciones basadas en ubicación, agrega la siguiente clave en `Info.plist`: + +```xml +NSLocationWhenInUseUsageDescription +We need your location to display on the map +``` + +## Estilos de Mapa + +Usa los presets estáticos de `ArcGISDesign` para establecer el estilo del mapa: + + + +Para crear un diseño desde un ID conocido, usa `ArcGISDesign.Create(id:)`: + + + +## Próximos Pasos + +Una vez que ArcGIS Maps SDK for Swift esté correctamente configurado, puedes usar `ArcGISMapView` o `ArcGISMapView2D` como se describe en la [documentación del componente Map View](/components/mapviewcomponent/). + +Para más ejemplos, consulta el tutorial [Comenzar](/es-419/get-started/). diff --git a/docs/src/content/docs/es-419/setup/google-maps.mdx b/docs/src/content/docs/es-419/setup/google-maps.mdx index dc58d09..e3873f9 100644 --- a/docs/src/content/docs/es-419/setup/google-maps.mdx +++ b/docs/src/content/docs/es-419/setup/google-maps.mdx @@ -7,6 +7,11 @@ head: content: Guía paso a paso para configurar Google Maps como proveedor en MapConductor iOS SDK. --- +import GoogleMapsInitExample from '~/components/setup/google-maps/GoogleMapsInitExample.astro'; +import GoogleMapsInitSdkInitializeExample from '~/components/setup/google-maps/GoogleMapsInitSdkInitializeExample.astro'; +import GoogleMapsVerificationExample from '~/components/setup/google-maps/GoogleMapsVerificationExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + Esta sección cubre el proceso de configuración para la integración del SDK de Google Maps con MapConductor. Usa este proveedor cuando tu aplicación ya depende del SDK de Google Maps para iOS o necesita los tipos de mapa de Google a través de `GoogleMapDesign`. @@ -40,33 +45,11 @@ Agrega el SDK de Google Maps y MapConductor a tu proyecto de Xcode: Llama a `GMSServices.provideAPIKey(_:)` una vez antes de que se muestre cualquier vista de mapa. El lugar recomendado es el struct `@main` de tu App: -```swift -import SwiftUI -import GoogleMaps -import MapConductorForGoogleMaps - -@main -struct MyApp: App { - init() { - GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` + Alternativamente, pasa la llamada mediante el parámetro `sdkInitialize` en `GoogleMapView`: -```swift -GoogleMapView( - state: mapState, - sdkInitialize: { GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY") } -) { ... } -``` + ### 4. Agregar Permisos de Ubicación (si es necesario) @@ -81,25 +64,7 @@ Si tu aplicación usa servicios de ubicación con Google Maps, agrega lo siguien Compila y ejecuta tu aplicación con el siguiente código para verificar la configuración: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject var mapState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 37.7749, longitude: -122.4194), - zoom: 12.0 - ) - ) - - var body: some View { - GoogleMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + Si el mapa se muestra sin errores, tu configuración está funcionando. Prueba las interacciones básicas (acercamiento, desplazamiento) para confirmar que todo funciona correctamente. @@ -135,19 +100,7 @@ Si necesitas funciones basadas en ubicación: 1. Agrega los permisos de ubicación en Info.plist (ver paso 4) 2. Solicita el permiso del usuario en tu aplicación: -```swift -import CoreLocation - -class LocationManager: NSObject, CLLocationManagerDelegate { - let manager = CLLocationManager() - - override init() { - super.init() - manager.delegate = self - manager.requestWhenInUseAuthorization() - } -} -``` + ## Próximos Pasos diff --git a/docs/src/content/docs/es-419/setup/here-maps.mdx b/docs/src/content/docs/es-419/setup/here-maps.mdx new file mode 100644 index 0000000..db49b5f --- /dev/null +++ b/docs/src/content/docs/es-419/setup/here-maps.mdx @@ -0,0 +1,112 @@ +--- +title: Configuración de HERE Maps +head: + - tag: meta + attrs: + name: description + content: Guía paso a paso para configurar HERE Maps como proveedor en MapConductor iOS SDK. +--- + +import HereMapsInitExample from '~/components/setup/here-maps/HereMapsInitExample.astro'; +import HereMapsInitSdkInitializeExample from '~/components/setup/here-maps/HereMapsInitSdkInitializeExample.astro'; +import HereMapsVerificationExample from '~/components/setup/here-maps/HereMapsVerificationExample.astro'; +import HereMapsStylePresetsExample from '~/components/setup/here-maps/HereMapsStylePresetsExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + +Esta sección cubre el proceso de configuración para la integración del SDK de HERE con MapConductor. + +Usa este proveedor cuando tu aplicación ya depende del SDK de HERE o necesita esquemas de mapa de HERE a través de `HereMapDesign`. + +## Requisitos Previos + +- Entorno de desarrollo iOS (Xcode 15+) +- Cuenta de HERE Platform +- ID de clave de acceso y secreto de clave de acceso de HERE SDK +- Objetivo de despliegue de iOS establecido en 16.0 o superior + +## Pasos de Configuración + +### 1. Configuración de HERE Platform + +1. Ve a [HERE Platform](https://platform.here.com/) +2. Crea un proyecto o selecciona un proyecto existente +3. Crea una clave de acceso para HERE SDK +4. Copia el ID de clave de acceso y el secreto de clave de acceso + +### 2. Configuración de Swift Package Manager + +Agrega MapConductorForHERE a tu proyecto de Xcode: + +1. En Xcode, ve a **File > Add Package Dependencies** +2. Ingresa la URL del paquete: + - `https://github.com/MapConductor/ios-for-here` + +### 3. Inicializar HERE SDK en tu Aplicación + +Llama a `initializeHERE(accessKeyId:accessKeySecret:)` una vez antes de que se muestre cualquier vista de mapa. El lugar recomendado es el struct `@main` de tu App: + + + +Alternativamente, pasa la llamada mediante el parámetro `sdkInitialize` en `HereMapView`: + + + +### 4. Agregar Permisos de Ubicación (Si es Necesario) + +Si tu aplicación usa servicios de ubicación con HERE SDK, agrega esto a `Info.plist`: + +```xml +NSLocationWhenInUseUsageDescription +We need your location to display on the map +``` + +## Verificación + +Compila y ejecuta tu aplicación con el siguiente código para verificar la configuración: + + + +Si el mapa se muestra sin errores, tu configuración está funcionando. Prueba las interacciones básicas (acercamiento, desplazamiento) para confirmar que todo funciona correctamente. + +## Solución de Problemas + +### Problemas Comunes + +**El mapa no se muestra (pantalla en blanco)** + +- Verifica que el ID de clave de acceso y el secreto de clave de acceso pasados a `initializeHERE(accessKeyId:accessKeySecret:)` sean correctos +- Comprueba que `initializeHERE(accessKeyId:accessKeySecret:)` se llame antes de que se cree cualquier vista de mapa +- Verifica que el objetivo de despliegue de iOS sea 16.0 o superior +- Asegúrate de que `MapConductorForHERE` esté correctamente vinculado a tu target + +**Errores de inicialización de HERE SDK** + +- Verifica los valores del ID de clave de acceso y del secreto de clave de acceso +- Asegúrate de que la clave no haya sido deshabilitada o eliminada +- Captura el error lanzado durante la inicialización y registra los detalles del error + +### Servicios de Ubicación + +Para habilitar funciones basadas en ubicación, agrega el permiso de ubicación en `Info.plist` (consulta el paso 4) y solicita autorización en tu aplicación: + + + +## Estilos de Mapa + +Usa los presets estáticos de `HereMapDesign` para establecer el estilo del mapa: + + + +## Próximos Pasos + +Una vez que HERE SDK esté correctamente configurado, puedes usar `HereMapView` como se describe en la [documentación del componente Map View](/components/mapviewcomponent/). + +Para más ejemplos, consulta el tutorial [Comenzar](/es-419/get-started/). diff --git a/docs/src/content/docs/es-419/setup/index.mdx b/docs/src/content/docs/es-419/setup/index.mdx index 1d17375..a118487 100644 --- a/docs/src/content/docs/es-419/setup/index.mdx +++ b/docs/src/content/docs/es-419/setup/index.mdx @@ -7,12 +7,12 @@ head: content: Configuración del MapConductor iOS SDK — instalación con Swift Package Manager para cada proveedor de mapas. --- +import PackageExample from '~/components/setup/overview/PackageExample.astro'; +import MinimalGoogleMapsExample from '~/components/setup/overview/MinimalGoogleMapsExample.astro'; + Usa `MapConductorCore` junto con uno o más paquetes de proveedor. -```swift -.package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), -.package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}") -``` + La configuración del SDK del proveedor sigue aplicándose: @@ -21,31 +21,10 @@ La configuración del SDK del proveedor sigue aplicándose: - MapKit: no requiere inicialización global del SDK - MapLibre: elige un `MapLibreDesign` - ArcGIS: usa los helpers de autenticación en `MapConductorForArcGIS` cuando se necesite autenticación +- HERE Maps: proporciona tu access key ID y secret al inicializar el HERE SDK ## Ejemplo Mínimo con Google Maps -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) - ) - - var body: some View { - GoogleMapView(state: mapViewState) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(label: "T") - ) - } - } -} -``` + Consulta las páginas de configuración específicas de cada proveedor para obtener detalles sobre los paquetes y las credenciales. diff --git a/docs/src/content/docs/es-419/setup/mapbox.mdx b/docs/src/content/docs/es-419/setup/mapbox.mdx index b236cf8..558b77b 100644 --- a/docs/src/content/docs/es-419/setup/mapbox.mdx +++ b/docs/src/content/docs/es-419/setup/mapbox.mdx @@ -7,6 +7,12 @@ head: content: Guía paso a paso para configurar Mapbox como proveedor en MapConductor iOS SDK. --- +import MapboxInitExample from '~/components/setup/mapbox/MapboxInitExample.astro'; +import MapboxInitSdkInitializeExample from '~/components/setup/mapbox/MapboxInitSdkInitializeExample.astro'; +import MapboxVerificationExample from '~/components/setup/mapbox/MapboxVerificationExample.astro'; +import MapboxStylePresetsExample from '~/components/setup/mapbox/MapboxStylePresetsExample.astro'; +import MapboxCustomStyleExample from '~/components/setup/mapbox/MapboxCustomStyleExample.astro'; + Esta sección cubre el proceso de configuración para la integración del SDK de Mapbox con MapConductor. Usa este proveedor cuando tu aplicación ya depende del SDK de Mapbox Maps o necesita URLs de estilos de Mapbox a través de `MapboxMapDesign`. @@ -40,57 +46,17 @@ El SDK de Mapbox Maps se instala automáticamente como dependencia — no se req Llama a `initializeMapbox(accessToken:)` una vez antes de que se muestre cualquier vista de mapa. El lugar recomendado es el struct `@main` de tu App: -```swift -import SwiftUI -import MapConductorForMapbox - -@main -struct MyApp: App { - init() { - initializeMapbox(accessToken: "YOUR_MAPBOX_PUBLIC_TOKEN") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` + Alternativamente, pasa la llamada mediante el parámetro `sdkInitialize` en `MapboxMapView`: -```swift -MapboxMapView( - state: mapState, - sdkInitialize: { initializeMapbox(accessToken: "YOUR_MAPBOX_PUBLIC_TOKEN") } -) { ... } -``` + ## Verificación Compila y ejecuta tu aplicación con el siguiente código para verificar la configuración: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForMapbox - -struct ContentView: View { - @StateObject var mapState = MapboxViewState( - mapDesignType: MapboxMapDesign.Standard, - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 37.7749, longitude: -122.4194), - zoom: 12.0 - ) - ) - - var body: some View { - MapboxMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + Si el mapa se muestra sin errores, tu configuración está funcionando. Prueba las interacciones básicas (acercamiento, desplazamiento) para confirmar que todo funciona correctamente. @@ -125,25 +91,16 @@ Para habilitar funciones basadas en ubicación, agrega la siguiente clave en `In Usa los presets estáticos de `MapboxMapDesign` para establecer el estilo del mapa: -```swift -// Estilo estándar (predeterminado) -mapState.mapDesignType = MapboxMapDesign.Standard - -// Estilo de calles -mapState.mapDesignType = MapboxMapDesign.Streets - -// Imágenes satelitales con carreteras y etiquetas -mapState.mapDesignType = MapboxMapDesign.SatelliteStreets - -// Estilo oscuro -mapState.mapDesignType = MapboxMapDesign.Dark -``` + Para un estilo personalizado, usa `MapboxMapDesign.custom(styleURI:)`: -```swift -mapState.mapDesignType = MapboxMapDesign.custom(styleURI: "mapbox://styles/user/abc123") -``` + ## Próximos Pasos diff --git a/docs/src/content/docs/es-419/setup/mapkit.mdx b/docs/src/content/docs/es-419/setup/mapkit.mdx index 5348b1f..6a6a945 100644 --- a/docs/src/content/docs/es-419/setup/mapkit.mdx +++ b/docs/src/content/docs/es-419/setup/mapkit.mdx @@ -7,6 +7,10 @@ head: content: Guía paso a paso para configurar Apple MapKit como proveedor en MapConductor iOS SDK. --- +import MapKitVerificationExample from '~/components/setup/mapkit/MapKitVerificationExample.astro'; +import MapKitStylePresetsExample from '~/components/setup/mapkit/MapKitStylePresetsExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + Esta sección cubre el proceso de configuración para la integración de MapKit con MapConductor. Usa este proveedor cuando quieras el SDK de mapas integrado de Apple sin dependencias externas ni @@ -42,26 +46,7 @@ Si tu aplicación usa servicios de ubicación, agrega la siguiente clave en `Inf Compila y ejecuta tu aplicación con el siguiente código para verificar la configuración: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForMapKit - -struct ContentView: View { - @StateObject var mapState = MapKitViewState( - mapDesignType: MapKitMapDesign.Standard, - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 12.0 - ) - ) - - var body: some View { - MapKitMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + Si el mapa se muestra sin errores, tu configuración está funcionando. Prueba las interacciones básicas (acercamiento, desplazamiento) para confirmar que todo funciona correctamente. @@ -85,37 +70,18 @@ Si el mapa se muestra sin errores, tu configuración está funcionando. Prueba l Para habilitar funciones basadas en ubicación, agrega los permisos de ubicación en `Info.plist` (ver paso 2) y solicita autorización en tu aplicación: -```swift -import CoreLocation - -class LocationManager: NSObject, CLLocationManagerDelegate { - let manager = CLLocationManager() - - override init() { - super.init() - manager.delegate = self - manager.requestWhenInUseAuthorization() - } -} -``` + ## Estilos de Mapa Usa los presets estáticos de `MapKitMapDesign` para establecer el estilo del mapa: -```swift -// Mapa de calles estándar -mapState.mapDesignType = MapKitMapDesign.Standard - -// Imágenes satelitales -mapState.mapDesignType = MapKitMapDesign.Satellite - -// Satélite con carreteras y etiquetas -mapState.mapDesignType = MapKitMapDesign.Hybrid - -// Estilo estándar atenuado -mapState.mapDesignType = MapKitMapDesign.MutedStandard -``` + ## Próximos Pasos diff --git a/docs/src/content/docs/es-419/setup/maplibre.mdx b/docs/src/content/docs/es-419/setup/maplibre.mdx index 0bd075f..00808e7 100644 --- a/docs/src/content/docs/es-419/setup/maplibre.mdx +++ b/docs/src/content/docs/es-419/setup/maplibre.mdx @@ -7,6 +7,11 @@ head: content: Guía paso a paso para configurar MapLibre como proveedor en MapConductor iOS SDK. --- +import MapLibreVerificationExample from '~/components/setup/maplibre/MapLibreVerificationExample.astro'; +import MapLibreStylePresetsExample from '~/components/setup/maplibre/MapLibreStylePresetsExample.astro'; +import MapLibreCustomStyleExample from '~/components/setup/maplibre/MapLibreCustomStyleExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + Esta sección cubre el proceso de configuración para la integración del SDK de MapLibre con MapConductor. Usa este proveedor cuando necesites un renderizador de mapas de código abierto sin una clave de API @@ -46,26 +51,7 @@ Si tu aplicación usa servicios de ubicación, agrega la siguiente clave en `Inf Compila y ejecuta tu aplicación con el siguiente código para verificar la configuración: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForMapLibre - -struct ContentView: View { - @StateObject var mapState = MapLibreViewState( - mapDesignType: MapLibreDesign.OsmBright, - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 12.0 - ) - ) - - var body: some View { - MapLibreMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + Si el mapa se muestra sin errores, tu configuración está funcionando. Prueba las interacciones básicas (acercamiento, desplazamiento) para confirmar que todo funciona correctamente. @@ -91,47 +77,22 @@ Si el mapa se muestra sin errores, tu configuración está funcionando. Prueba l Para habilitar funciones basadas en ubicación, agrega los permisos de ubicación en `Info.plist` (ver paso 2) y solicita autorización en tu aplicación: -```swift -import CoreLocation - -class LocationManager: NSObject, CLLocationManagerDelegate { - let manager = CLLocationManager() - - override init() { - super.init() - manager.delegate = self - manager.requestWhenInUseAuthorization() - } -} -``` + ## Estilos de Mapa Usa los presets estáticos de `MapLibreDesign` para establecer el estilo del mapa: -```swift -// OpenStreetMap Bright (inglés) -mapState.mapDesignType = MapLibreDesign.OsmBright - -// OpenStreetMap Bright (japonés) -mapState.mapDesignType = MapLibreDesign.OsmBrightJa - -// MapTiler Toner (inglés) -mapState.mapDesignType = MapLibreDesign.MapTilerTonerEn - -// Tiles de demostración ligeros -mapState.mapDesignType = MapLibreDesign.DemoTiles -``` + Para un estilo personalizado, construye un `MapLibreDesign` directamente: -```swift -let custom = MapLibreDesign( - id: "custom", - styleJsonURL: "https://example.com/style.json" -) -mapState.mapDesignType = custom -``` + ## Próximos Pasos diff --git a/docs/src/content/docs/es-419/states/circle-state.mdx b/docs/src/content/docs/es-419/states/circle-state.mdx index d02c8b2..410e276 100644 --- a/docs/src/content/docs/es-419/states/circle-state.mdx +++ b/docs/src/content/docs/es-419/states/circle-state.mdx @@ -7,6 +7,9 @@ head: content: CircleState — actualiza dinámicamente el radio, relleno y borde de un círculo en MapConductor iOS SDK. --- +import CircleStateBasicExample from '~/components/states/circle-state/CircleStateBasicExample.astro'; +import CircleStateSelectionExample from '~/components/states/circle-state/CircleStateSelectionExample.astro'; + `CircleState` es un `ObservableObject` usado por `Circle(state:)`. Úsalo cuando el centro, el radio, el estilo, el z-index o el comportamiento de clic del círculo cambien después de que el contenido del mapa se haya renderizado por primera vez. @@ -27,27 +30,10 @@ head: Usa `radiusMeters` para el tamaño del círculo. Usa el canal alfa en `fillColor` o `strokeColor` para la transparencia. -```swift -let circleState = CircleState( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500, - fillColor: UIColor.blue.withAlphaComponent(0.2) -) - -Circle(state: circleState) - -circleState.radiusMeters = 1_000 -circleState.center = GeoPoint(latitude: 35.6895, longitude: 139.6917) -``` + ## Ejemplo de selección -```swift -circleState.onClick = { event in - selectedCircleId = event.state.id -} - -circleState.strokeColor = selectedCircleId == circleState.id ? .red : .blue -``` + Mantén `id` estable cuando un círculo represente un objeto del modelo, como una geocerca o un área de búsqueda. diff --git a/docs/src/content/docs/es-419/states/groundimage-state.mdx b/docs/src/content/docs/es-419/states/groundimage-state.mdx index a721cf4..eb80a6f 100644 --- a/docs/src/content/docs/es-419/states/groundimage-state.mdx +++ b/docs/src/content/docs/es-419/states/groundimage-state.mdx @@ -7,6 +7,9 @@ head: content: GroundImageState — actualiza dinámicamente los límites, opacidad y fuente de imagen en MapConductor iOS SDK. --- +import GroundImageStateBasicExample from '~/components/states/groundimage-state/GroundImageStateBasicExample.astro'; +import GroundImageStateRepositionExample from '~/components/states/groundimage-state/GroundImageStateRepositionExample.astro'; + `GroundImageState` es un `ObservableObject` usado por `GroundImage(state:)`. Úsalo cuando la imagen, la opacidad, los límites geográficos o el manejador de clic cambien en tiempo de ejecución. @@ -21,32 +24,12 @@ head: - `extra: Any?` - `onClick: OnGroundImageEventHandler?` -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) - -let groundImageState = GroundImageState( - bounds: bounds, - image: UIImage(named: "overlay")!, - opacity: 0.5 -) - -GroundImage(state: groundImageState) - -groundImageState.opacity = 1.0 -``` + ## Reposicionamiento Actualiza `bounds` para mover o cambiar el tamaño de la imagen: -```swift -groundImageState.bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.66, longitude: 139.73), - northEast: GeoPoint(latitude: 35.70, longitude: 139.77) -) -``` + Actualiza `image` cuando cambie el ráster mostrado. Usa `tileSize` para controlar el tamaño de tesela utilizado por el renderizador de superposición. diff --git a/docs/src/content/docs/es-419/states/marker-state.mdx b/docs/src/content/docs/es-419/states/marker-state.mdx index f2fc8d8..feff4aa 100644 --- a/docs/src/content/docs/es-419/states/marker-state.mdx +++ b/docs/src/content/docs/es-419/states/marker-state.mdx @@ -7,6 +7,9 @@ head: content: MarkerState — actualiza dinámicamente la posición, ícono y visibilidad de marcadores en MapConductor iOS SDK. --- +import MarkerStateBasicExample from '~/components/states/marker-state/MarkerStateBasicExample.astro'; +import MarkerStateHandlersExample from '~/components/states/marker-state/MarkerStateHandlersExample.astro'; + `MarkerState` es un `ObservableObject` usado por `Marker(state:)`. Usa `MarkerState` cuando los datos del marcador necesiten cambiar después de que el marcador sea creado. Para marcadores estáticos, el inicializador directo `Marker(position:...)` generalmente es suficiente. @@ -28,22 +31,7 @@ Usa `MarkerState` cuando los datos del marcador necesiten cambiar después de qu Usa `extra` para datos específicos de la app e `icon` para texto o imagen visible del marcador. -```swift -let markerState = MarkerState( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - extra: "Tokyo Station", - icon: DefaultMarkerIcon(fillColor: .red, label: "T"), - draggable: true, - onClick: { state in - print("Clicked \(state.id), extra: \(String(describing: state.extra))") - } -) - -Marker(state: markerState) - -markerState.position = GeoPoint(latitude: 35.6895, longitude: 139.6917) -markerState.animate(.Bounce) -``` + Usa `copy(...)` para crear un nuevo estado de marcador con valores seleccionados modificados. @@ -51,15 +39,7 @@ Usa `copy(...)` para crear un nuevo estado de marcador con valores seleccionados Los manejadores de eventos se almacenan en el estado, por lo que pueden reemplazarse junto con otras propiedades del marcador: -```swift -markerState.onClick = { state in - selectedMarkerId = state.id -} - -markerState.onDragEnd = { state in - saveCoordinate(state.position) -} -``` + ## Identidad diff --git a/docs/src/content/docs/es-419/states/polygon-state.mdx b/docs/src/content/docs/es-419/states/polygon-state.mdx index 5de8ce7..8ac13ef 100644 --- a/docs/src/content/docs/es-419/states/polygon-state.mdx +++ b/docs/src/content/docs/es-419/states/polygon-state.mdx @@ -7,6 +7,9 @@ head: content: PolygonState — actualiza dinámicamente los puntos, relleno y borde de un polígono en MapConductor iOS SDK. --- +import PolygonStateBasicExample from '~/components/states/polygon-state/PolygonStateBasicExample.astro'; +import PolygonStateHolesExample from '~/components/states/polygon-state/PolygonStateHolesExample.astro'; + `PolygonState` es un `ObservableObject` usado por `Polygon(state:)`. Úsalo cuando un área rellena cambie de forma, estilo, huecos, z-index o estado de selección después de su creación. @@ -26,25 +29,12 @@ head: Usa el canal alfa en `fillColor` o `strokeColor` para la transparencia. -```swift -let polygonState = PolygonState( - points: outerRing, - fillColor: UIColor.green.withAlphaComponent(0.25), - holes: [innerRing] -) - -Polygon(state: polygonState) - -polygonState.strokeWidth = 3 -polygonState.fillColor = UIColor.red.withAlphaComponent(0.2) -``` + ## Actualizar huecos `holes` es un arreglo de anillos dentro del polígono exterior: -```swift -polygonState.holes = [restrictedAreaRing] -``` + Usa el canal alfa en `fillColor` para regiones translúcidas. Mantén `points` y `holes` en el orden de coordenadas requerido por tu modelo de datos. diff --git a/docs/src/content/docs/es-419/states/polyline-state.mdx b/docs/src/content/docs/es-419/states/polyline-state.mdx index 3da008e..cd801ff 100644 --- a/docs/src/content/docs/es-419/states/polyline-state.mdx +++ b/docs/src/content/docs/es-419/states/polyline-state.mdx @@ -7,6 +7,9 @@ head: content: PolylineState — actualiza dinámicamente los puntos y trazo de una polilínea en MapConductor iOS SDK. --- +import PolylineStateBasicExample from '~/components/states/polyline-state/PolylineStateBasicExample.astro'; +import PolylineStateRouteUpdateExample from '~/components/states/polyline-state/PolylineStateRouteUpdateExample.astro'; + `PolylineState` es un `ObservableObject` usado por `Polyline(state:)`. Úsalo cuando una ruta, trayectoria o estilo de línea necesite actualizarse sin recrear la declaración de superposición. @@ -21,29 +24,12 @@ head: - `extra: Any?` - `onClick: OnPolylineEventHandler?` -```swift -let polylineState = PolylineState( - points: [ - GeoPoint(latitude: 35.6812, longitude: 139.7671), - GeoPoint(latitude: 35.6586, longitude: 139.7454) - ], - strokeColor: .blue, - strokeWidth: 4 -) - -Polyline(state: polylineState) - -polylineState.points.append(GeoPoint(latitude: 35.7101, longitude: 139.8107)) -polylineState.strokeColor = .red -``` + ## Actualizaciones de ruta Reemplaza el arreglo completo de puntos cuando se recalcule una ruta: -```swift -polylineState.points = recalculatedRoute -polylineState.strokeWidth = 5 -``` + Usa `onClick` cuando el usuario deba poder seleccionar una ruta o inspeccionar metadatos de línea almacenados en `extra`. diff --git a/docs/src/content/docs/event/event-handlers.mdx b/docs/src/content/docs/event/event-handlers.mdx index f4010cc..a1c7603 100644 --- a/docs/src/content/docs/event/event-handlers.mdx +++ b/docs/src/content/docs/event/event-handlers.mdx @@ -7,35 +7,13 @@ head: content: Map and overlay event handlers including onMapClick, onMarkerClick, and onDragEnd in MapConductor iOS SDK. --- +import EventHandlersMapExample from '~/components/event/EventHandlersMapExample.astro'; +import EventHandlersMarkerExample from '~/components/event/EventHandlersMarkerExample.astro'; +import EventHandlersShapeExample from '~/components/event/EventHandlersShapeExample.astro'; + Map event handlers are passed to each provider-specific map view initializer. They are not SwiftUI view modifiers. -```swift -GoogleMapView( - state: mapViewState, - onMapLoaded: { state in - print("Loaded: \(state.id)") - }, - onMapClick: { point in - print("Clicked: \(point.latitude), \(point.longitude)") - }, - onCameraMoveStart: { camera in - print("Move started: \(camera.zoom)") - }, - onCameraMove: { camera in - print("Moving: \(camera.position)") - }, - onCameraMoveEnd: { camera in - print("Move ended: \(camera.zoom)") - } -) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - onClick: { marker in - print("Marker clicked: \(marker.id)") - } - ) -} -``` + The same initializer arguments are available on `GoogleMapView`, `MapboxMapView`, `MapKitMapView`, `MapLibreMapView`, and `ArcGISMapView`. @@ -50,45 +28,12 @@ The same initializer arguments are available on `GoogleMapView`, `MapboxMapView` - `onAnimateStart: (MarkerState) -> Void` - `onAnimateEnd: (MarkerState) -> Void` -```swift -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - draggable: true, - onClick: { state in - print("Clicked marker \(state.id)") - }, - onDragEnd: { state in - print("Dropped at \(state.position)") - } -) -``` + ## Shape Events Shape click handlers are configured on the overlay or state initializer: -```swift -Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500, - onClick: { event in - print("Circle clicked at \(event.clicked)") - } -) - -Polyline( - points: route, - onClick: { event in - print("Polyline clicked at \(event.clicked)") - } -) - -Polygon( - points: polygon, - onClick: { event in - print("Polygon clicked at \(event.clicked)") - } -) -``` + `GroundImage` also has `onClick: (GroundImageEvent) -> Void`. `RasterLayerState` defines a `RasterLayerEvent` type, but `RasterLayer` itself does not expose an `onClick` initializer. diff --git a/docs/src/content/docs/event/onMapLoaded.mdx b/docs/src/content/docs/event/onMapLoaded.mdx index be3cfa4..c025daa 100644 --- a/docs/src/content/docs/event/onMapLoaded.mdx +++ b/docs/src/content/docs/event/onMapLoaded.mdx @@ -7,65 +7,27 @@ head: content: onMapLoaded callback — run code after the map and provider finish loading in MapConductor iOS SDK. --- +import OnMapLoadedPackagesExample from '~/components/event/OnMapLoadedPackagesExample.astro'; +import OnMapLoadedGoogleMapsInitExample from '~/components/event/OnMapLoadedGoogleMapsInitExample.astro'; +import OnMapLoadedMapboxInitExample from '~/components/event/OnMapLoadedMapboxInitExample.astro'; +import OnMapLoadedMapLibreExample from '~/components/event/OnMapLoadedMapLibreExample.astro'; +import OnMapLoadedCallbackExample from '~/components/event/OnMapLoadedCallbackExample.astro'; + Add the core package and the provider packages you use: -```swift -dependencies: [ - .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}"), -], -targets: [ - .target( - name: "YourApp", - dependencies: [ - .product(name: "MapConductorCore", package: "ios-sdk-core"), - .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), - ] - ), -] -``` + ## Google Maps Call `GMSServices.provideAPIKey(_:)` before displaying `GoogleMapView`. -```swift -import GoogleMaps - -@main -struct MyApp: App { - init() { - GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` + ## Mapbox Call `initializeMapbox(accessToken:)` before displaying `MapboxMapView`, or pass it through the view's `sdkInitialize` parameter. -```swift -import MapConductorForMapbox - -@main -struct MyApp: App { - init() { - initializeMapbox(accessToken: "YOUR_MAPBOX_PUBLIC_TOKEN") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` + ## MapKit @@ -75,27 +37,10 @@ MapKit does not require SDK initialization. MapLibre has no global initialization in this package. Choose a `MapLibreDesign` through `MapLibreViewState.mapDesignType`. -```swift -let state = MapLibreViewState( - mapDesignType: MapLibreDesign.OsmBright, - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) -) -``` + ## onMapLoaded `onMapLoaded` is an initializer parameter on provider map views: -```swift -GoogleMapView( - state: mapViewState, - onMapLoaded: { state in - print("Map loaded: \(state.id)") - } -) { - Marker(position: GeoPoint(latitude: 35.6812, longitude: 139.7671)) -} -``` + diff --git a/docs/src/content/docs/experimental/geojson-layer.mdx b/docs/src/content/docs/experimental/geojson-layer.mdx new file mode 100644 index 0000000..7a2499f --- /dev/null +++ b/docs/src/content/docs/experimental/geojson-layer.mdx @@ -0,0 +1,65 @@ +--- +title: GeoJSON Layer +head: + - tag: meta + attrs: + name: description + content: Render GeoJSON data as a tile-based map layer using MapConductorGeoJSON in the iOS SDK. +--- + +import GeoJSONPackageInstall from '~/components/experimental/geojson-layer/GeoJSONPackageInstall.astro'; +import GeoJSONProductDep from '~/components/experimental/geojson-layer/GeoJSONProductDep.astro'; +import GeoJSONBasicExample from '~/components/experimental/geojson-layer/GeoJSONBasicExample.astro'; +import GeoJSONLayerStateInit from '~/components/experimental/geojson-layer/GeoJSONLayerStateInit.astro'; +import GeoJSONLayerStateUpdate from '~/components/experimental/geojson-layer/GeoJSONLayerStateUpdate.astro'; +import GeoJSONLayerStyle from '~/components/experimental/geojson-layer/GeoJSONLayerStyle.astro'; +import GeoJSONLoadFeatures from '~/components/experimental/geojson-layer/GeoJSONLoadFeatures.astro'; +import GeoJSONFeatureState from '~/components/experimental/geojson-layer/GeoJSONFeatureState.astro'; +import GeoJSONClickHandler from '~/components/experimental/geojson-layer/GeoJSONClickHandler.astro'; +import GeoJSONClickHandlerMap from '~/components/experimental/geojson-layer/GeoJSONClickHandlerMap.astro'; + +`MapConductorGeoJSON` renders GeoJSON feature collections as a tile-based raster layer. It works with all map providers through the standard `RasterLayer` integration. + +## Package + + + +Add the product to your target: + + + +## Basic Usage + + + +## GeoJSONLayerState + +`GeoJSONLayerState` manages the tile server and rendering pipeline. + + + +Properties can be updated at runtime: + + + +## LayerStyle + + + +## Loading Features + +Pass a `[GeoJSONFeature]` array directly to `GeoJSONLayer`. The layer re-renders tiles whenever the features list changes. + + + +For observable feature state with per-feature visibility: + + + +## Click Handling + + + +Call `layerState.processClick(geoPoint:)` from your map's `onMapClick` handler to route taps through the GeoJSON hit test: + + diff --git a/docs/src/content/docs/experimental/heatmap.mdx b/docs/src/content/docs/experimental/heatmap.mdx index aaf73bd..d268001 100644 --- a/docs/src/content/docs/experimental/heatmap.mdx +++ b/docs/src/content/docs/experimental/heatmap.mdx @@ -7,6 +7,13 @@ head: content: Tile-based heatmap overlay that works with any map provider in MapConductor iOS SDK. --- +import HeatmapPackageInstall from '~/components/experimental/heatmap/HeatmapPackageInstall.astro'; +import HeatmapProductDep from '~/components/experimental/heatmap/HeatmapProductDep.astro'; +import HeatmapBasicExample from '~/components/experimental/heatmap/HeatmapBasicExample.astro'; +import HeatmapBatchPoints from '~/components/experimental/heatmap/HeatmapBatchPoints.astro'; +import HeatmapDynamicState from '~/components/experimental/heatmap/HeatmapDynamicState.astro'; +import HeatmapCustomGradient from '~/components/experimental/heatmap/HeatmapCustomGradient.astro'; + The `MapConductorHeatmap` module provides a map-implementation-agnostic heatmap overlay. It renders heatmap data as a raster layer and works with any map provider. Use heatmaps when each point has a numeric weight and the user needs to understand density or intensity over an area rather than inspect individual markers. @@ -15,53 +22,23 @@ Use heatmaps when each point has a numeric weight and the user needs to understa Add to your Package.swift: -```swift -.package(url: "https://github.com/MapConductor/ios-heatmap", from: "{HEATMAP_MODULE_VERSION}"), -``` + In your target dependencies: -```swift -.product(name: "MapConductorHeatmap", package: "ios-heatmap"), -``` + ## Basic Usage Use `HeatmapPointView` for a small number of points or when the points are naturally declared as SwiftUI content inside the overlay: -```swift -import MapConductorHeatmap - -GoogleMapView(state: mapViewState) { - HeatmapOverlay( - radiusPx: 20, - opacity: 0.7 - ) { - HeatmapPointView( - position: GeoPoint(latitude: 35.6762, longitude: 139.6503), - weight: 1.0 - ) - HeatmapPointView( - position: GeoPoint(latitude: 35.6895, longitude: 139.6917), - weight: 2.5 - ) - } -} -``` + ## Batch Points For large datasets, use `HeatmapPoints` to add all points in a single update. This avoids many separate point appear/disappear updates: -```swift -let pointStates: [HeatmapPointState] = [...] - -GoogleMapView(state: mapViewState) { - HeatmapOverlay(radiusPx: 20) { - HeatmapPoints(pointStates) - } -} -``` + `HeatmapPoint` is the internal data value used by the renderer. In map content, use `HeatmapPointView` or `HeatmapPoints`. @@ -69,51 +46,11 @@ GoogleMapView(state: mapViewState) { Use `HeatmapOverlayState` to control heatmap properties at runtime: -```swift -struct DynamicHeatmapMap: View { - @StateObject private var heatmapState = HeatmapOverlayState( - radiusPx: 20, - opacity: 0.7 - ) - @StateObject private var mapViewState = GoogleMapViewState() - - var body: some View { - VStack { - GoogleMapView(state: mapViewState) { - HeatmapOverlay(state: heatmapState) { - HeatmapPoints(pointStates) - } - } - - HStack { - Text("Radius:") - Slider(value: .init( - get: { Double(heatmapState.radiusPx) }, - set: { heatmapState.radiusPx = Int($0) } - ), in: 10...50) - - Text("Opacity:") - Slider(value: $heatmapState.opacity, in: 0...1) - } - .padding() - } - } -} -``` + ## Custom Gradient -```swift -let gradient = HeatmapGradient( - stops: [ - HeatmapGradientStop(position: 0.0, color: UIColor(red: 0, green: 0, blue: 1, alpha: 1)), // Blue - HeatmapGradientStop(position: 0.5, color: UIColor(red: 0, green: 1, blue: 0, alpha: 1)), // Green - HeatmapGradientStop(position: 1.0, color: UIColor(red: 1, green: 0, blue: 0, alpha: 1)) // Red - ] -) - -let heatmapState = HeatmapOverlayState(gradient: gradient) -``` + `HeatmapOverlayState` also supports `maxIntensity`, `weightProvider`, `tileSize`, `trackPointUpdates`, and `disableTileServerCache` through its initializers. Use those when you need consistent intensity scaling, custom weighting, or tile-cache behavior for frequently changing datasets. diff --git a/docs/src/content/docs/experimental/marker-clustering.mdx b/docs/src/content/docs/experimental/marker-clustering.mdx index f3d66f0..484c416 100644 --- a/docs/src/content/docs/experimental/marker-clustering.mdx +++ b/docs/src/content/docs/experimental/marker-clustering.mdx @@ -7,51 +7,25 @@ head: content: Automatic marker clustering that groups nearby markers into clusters in MapConductor iOS SDK. --- +import MarkerClusterPackageInstall from '~/components/experimental/marker-clustering/MarkerClusterPackageInstall.astro'; +import MarkerClusterProductDep from '~/components/experimental/marker-clustering/MarkerClusterProductDep.astro'; +import MarkerClusterBasicExample from '~/components/experimental/marker-clustering/MarkerClusterBasicExample.astro'; +import MarkerClusterOnClick from '~/components/experimental/marker-clustering/MarkerClusterOnClick.astro'; +import MarkerClusterState from '~/components/experimental/marker-clustering/MarkerClusterState.astro'; + The marker clustering package product and module name is `MapConductorMarkerCluster`. Use marker clustering when a screen can display many markers at the same time and individual pins would overlap or become expensive to update. The module replaces groups of nearby source markers with cluster markers based on the current camera and viewport. -```swift -.package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "{MARKER_CLUSTERING_MODULE_VERSION}") -``` + -```swift -.product(name: "MapConductorMarkerCluster", package: "ios-marker-cluster") -``` + ## Basic Usage `MarkerClusterGroup` is generic over the provider's actual marker type. Use the type alias from the provider module. -```swift -import MapConductorCore -import MapConductorForGoogleMaps -import MapConductorMarkerCluster - -struct ClusterMap: View { - @StateObject private var mapViewState = GoogleMapViewState() - - @State private var clusterStrategy = - MarkerClusterStrategy( - clusterRadiusPx: 60, - minClusterSize: 2, - enableZoomAnimation: true, - enablePanAnimation: true - ) - - let markers: [MarkerState] - - var body: some View { - GoogleMapView(state: mapViewState) { - MarkerClusterGroup(strategy: clusterStrategy) { - ForArray(markers) { marker in - Marker(state: marker) - } - } - } - } -} -``` + `MarkerClusterGroup` extracts `MarkerState` values from the content closure, applies the rendering strategy, and passes through other overlay content. Keep marker ids stable so clustering can track source markers across updates. @@ -83,41 +57,12 @@ Provider type aliases currently include: Use `onClusterClick` to respond when the user taps a cluster: -```swift -MarkerClusterGroup( - onClusterClick: { cluster in - print("Cluster clicked: \(cluster.count)") - } -) { - ForArray(markers) { marker in - Marker(state: marker) - } -} -``` + ## Cluster State -Use `MarkerClusterGroupState` when you want observable clustering configuration or debug radius circles: - -```swift -@StateObject private var clusterState = - MarkerClusterGroupState( - clusterRadiusPx: 60, - minClusterSize: 2 - ) - -MarkerClusterGroup(state: clusterState) { - ForArray(markers) { marker in - Marker(state: marker) - } -} -``` - -`MarkerClusterGroupState` exposes debug-radius display properties: +Use `MarkerClusterGroupState` when you want observable clustering configuration: -- `showClusterRadiusCircle` -- `clusterRadiusStrokeColor` -- `clusterRadiusStrokeWidth` -- `clusterRadiusFillColor` + -These are useful while tuning `clusterRadiusPx` and `expandMargin`. +`debugHullPolygons` renders each cluster's convex hull while you tune `clusterRadiusPx` and `expandMargin`. It is the only debug switch the clustering API exposes, and it is the same on Android, iOS and React. diff --git a/docs/src/content/docs/get-started.mdx b/docs/src/content/docs/get-started.mdx index bea17eb..e51e696 100644 --- a/docs/src/content/docs/get-started.mdx +++ b/docs/src/content/docs/get-started.mdx @@ -7,7 +7,13 @@ head: content: Quick-start tutorial for MapConductor iOS SDK — add a map view and marker in minutes. --- -This tutorial shows a small SwiftUI map screen using APIs that exist in the current iOS source. It starts with Google Maps for concreteness, then points out the provider types you would swap when using another map SDK. +import GetStartedPackagesExample from '~/components/get-started/GetStartedPackagesExample.astro'; +import GetStartedProductsExample from '~/components/get-started/GetStartedProductsExample.astro'; +import GetStartedDisplayMapExample from '~/components/get-started/GetStartedDisplayMapExample.astro'; +import GetStartedHandleInteractionsExample from '~/components/get-started/GetStartedHandleInteractionsExample.astro'; +import GetStartedMoveCameraExample from '~/components/get-started/GetStartedMoveCameraExample.astro'; + +This tutorial shows a small SwiftUI map screen using APIs that exist in the current iOS source. It starts with MapKit for concreteness, since it is a system framework on iOS and needs no API key or other native setup, then points out the provider types you would swap when using another map SDK. ## What You Will Build @@ -20,92 +26,29 @@ This tutorial shows a small SwiftUI map screen using APIs that exist in the curr - Xcode with Swift Package Manager support - A SwiftUI iOS application -- The native setup for the provider you choose, such as a Google Maps API key or Mapbox access token +- The native setup for the provider you choose, such as a Google Maps API key or Mapbox access token (not needed for MapKit) ## Add Packages Add the core package and one provider package: -```swift -.package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), -.package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}") -``` + Then add products to your target: -```swift -.product(name: "MapConductorCore", package: "ios-sdk-core"), -.product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps") -``` + Only add the provider modules you use. Optional modules such as `MapConductorHeatmap` and `MapConductorMarkerCluster` can be added later when a screen needs those overlays. -## Initialize Google Maps - -```swift -import GoogleMaps +## No Native Setup Required -@main -struct ExampleApp: App { - init() { - GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` +MapKit is a system framework, so there is no API key, access token, or SDK initialization step. You can go straight to displaying a map. For other providers, perform the native SDK setup required by that provider before displaying the map. MapConductor does not replace provider API keys, access tokens, licenses, or tile configuration. ## Display a Map -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) - ) - - var body: some View { - GoogleMapView( - state: mapViewState, - onMapClick: { point in - print("Map clicked at \(point)") - } - ) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(fillColor: .red, label: "T"), - onClick: { marker in - print("Marker clicked: \(marker.id)") - } - ) - - Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 1_000, - fillColor: UIColor.blue.withAlphaComponent(0.2) - ) - - Polyline(points: [ - GeoPoint(latitude: 35.6812, longitude: 139.7671), - GeoPoint(latitude: 35.6586, longitude: 139.7454) - ]) - } - .ignoresSafeArea() - } -} -``` + The map view owns the native map instance. The `@StateObject` owns cross-provider map state such as the current camera position and selected map design. @@ -115,45 +58,13 @@ The content closure is built with `MapViewContentBuilder`. It accepts overlay it Map events are initializer parameters on the provider map view: -```swift -GoogleMapView( - state: mapViewState, - onMapLoaded: { state in - print("Loaded map \(state.id)") - }, - onMapClick: { point in - print("Tapped \(point.latitude), \(point.longitude)") - }, - onCameraMoveEnd: { camera in - print("Camera ended at zoom \(camera.zoom)") - } -) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - draggable: true, - onClick: { marker in - print("Marker clicked: \(marker.id)") - }, - onDragEnd: { marker in - print("Marker dropped at \(marker.position)") - } - ) -} -``` - -These are not SwiftUI modifiers. Use the initializer parameters directly on `GoogleMapView`, `MapboxMapView`, `MapKitMapView`, `MapLibreMapView`, or `ArcGISMapView`. + + +These are not SwiftUI modifiers. Use the initializer parameters directly on `MapKitMapView`, `GoogleMapView`, `MapboxMapView`, `MapLibreMapView`, or `ArcGISMapView`. ## Move the Camera -```swift -mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6586, longitude: 139.7454), - zoom: 15 - ), - durationMillis: 500 -) -``` + If your app needs to frame a set of points, calculate the camera center and zoom in your own code and then call `moveCameraTo(cameraPosition:durationMillis:)`. diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index fbe6698..5c3ba8b 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -14,6 +14,18 @@ import { CardGrid, Card } from "@astrojs/starlight/components"; import Features from "~/components/index/Features.astro"; import MapConductorIntroduction from "~/components/index/MapConductorIntroduction.astro"; import TopCards from "~/components/index/TopCards.astro"; +import { Image } from "astro:assets"; +import topBanner from "~/assets/top-page/en-comic-why-mapconductor.jpg"; + + + +MapConductor — a unified Android maps SDK that lets you work with multiple map providers through a single API -# About MapConductor - - ## Supported Features ## Provider Packages diff --git a/docs/src/content/docs/ja/components/circle.mdx b/docs/src/content/docs/ja/components/circle.mdx index cf7594b..1fe4e73 100644 --- a/docs/src/content/docs/ja/components/circle.mdx +++ b/docs/src/content/docs/ja/components/circle.mdx @@ -7,40 +7,20 @@ head: content: MapConductor iOS SDKのCircle — 半径・塗りつぶし・枠線を設定できる円形オーバーレイの描画方法 --- +import CircleBasicExample from '~/components/components/circle/CircleBasicExample.astro'; +import CircleInitializer from '~/components/components/circle/CircleInitializer.astro'; +import CircleClickExample from '~/components/components/circle/CircleClickExample.astro'; +import CircleStateExample from '~/components/components/circle/CircleStateExample.astro'; + `Circle` は円形のオーバーレイを描画します。 サービスゾーン、検索範囲、ジオフェンスなど、半径ベースのエリアにサークルを使用してください。半径は `radiusMeters` を通じてメートル単位で指定します。 -```swift -Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 1_000, - strokeColor: .red, - strokeWidth: 2, - fillColor: UIColor.red.withAlphaComponent(0.2), - onClick: { event in - print("Clicked at \(event.clicked)") - } -) -``` + ## イニシャライザ -```swift -Circle( - center: GeoPointProtocol, - radiusMeters: Double, - geodesic: Bool = true, - clickable: Bool = true, - strokeColor: UIColor = .red, - strokeWidth: Double = 1.0, - fillColor: UIColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5), - id: String? = nil, - zIndex: Int? = nil, - extra: Any? = nil, - onClick: OnCircleEventHandler? = nil -) -``` + 透明度には `fillColor` または `strokeColor` のアルファコンポーネントを使用してください。 @@ -48,29 +28,10 @@ Circle( ユーザーがサークルを選択または確認できるようにする場合は、`clickable` と `onClick` を設定してください。イベントにはクリックされた座標が含まれます。 -```swift -Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 750, - clickable: true, - onClick: { event in - print("Circle clicked at \(event.clicked)") - } -) -``` + ## ステートの更新 -```swift -let state = CircleState( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500 -) - -Circle(state: state) - -state.radiusMeters = 1_000 -state.fillColor = UIColor.blue.withAlphaComponent(0.2) -``` + オーバーレイの作成後に半径、中心、色、z インデックス、またはクリックハンドラーが変わる場合は、ステートオブジェクトを使用してください。 diff --git a/docs/src/content/docs/ja/components/groundimage.mdx b/docs/src/content/docs/ja/components/groundimage.mdx index 51601bf..aa094e8 100644 --- a/docs/src/content/docs/ja/components/groundimage.mdx +++ b/docs/src/content/docs/ja/components/groundimage.mdx @@ -7,59 +7,31 @@ head: content: MapConductor iOS SDKのGroundImage — 地図上に地理参照画像をオーバーレイする方法 --- +import GroundImageBasicExample from '~/components/components/groundimage/GroundImageBasicExample.astro'; +import GroundImageInitializer from '~/components/components/groundimage/GroundImageInitializer.astro'; +import GroundImageStateExample from '~/components/components/groundimage/GroundImageStateExample.astro'; +import GroundImageOpacityExample from '~/components/components/groundimage/GroundImageOpacityExample.astro'; +import GroundImageClickExample from '~/components/components/groundimage/GroundImageClickExample.astro'; + `GroundImage` は地理的な矩形の上に `UIImage` を配置します。 フロアプラン、古地図、図面、カスタムラスターアノテーションなど、固定された地理的オーバーレイにグラウンドイメージを使用してください。画像は `GeoRectBounds` によって位置が決まります。 -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) - -GroundImage( - bounds: bounds, - image: UIImage(named: "overlay")!, - opacity: 0.7, - tileSize: 512, - onClick: { event in - print("Ground image clicked: \(String(describing: event.clicked))") - } -) -``` + ## イニシャライザ -```swift -GroundImage( - bounds: GeoRectBounds, - image: UIImage, - opacity: Double = 1.0, - tileSize: Int = 512, - id: String? = nil, - extra: Any? = nil, - onClick: OnGroundImageEventHandler? = nil -) -``` + ## ステートの更新 -```swift -let state = GroundImageState(bounds: bounds, image: image, opacity: 0.5) - -GroundImage(state: state) - -state.opacity = 1.0 -state.bounds = updatedBounds -``` + ## 不透明度と更新 `GroundImageState` は `opacity` を公開しているため、オーバーレイを置き換えることなく画像をフェードイン・フェードアウトできます。 -```swift -state.opacity = 0.35 -``` + 元の画像が変わった場合は `state.image` を更新してください。地理的な範囲が変わった場合は `state.bounds` を更新してください。 @@ -67,14 +39,6 @@ state.opacity = 0.35 オーバーレイを選択可能にする場合は `onClick` を使用してください。 -```swift -GroundImage( - bounds: bounds, - image: image, - onClick: { event in - print("Clicked image: \(event.state.id)") - } -) -``` + 画像オーバーレイに対するプロバイダーのサポートは異なる場合があります。アプリで使用する画像サイズと不透明度の値について、対象のプロバイダーモジュールでテストしてください。 diff --git a/docs/src/content/docs/ja/components/infobubble.mdx b/docs/src/content/docs/ja/components/infobubble.mdx index 0cfac24..eea168c 100644 --- a/docs/src/content/docs/ja/components/infobubble.mdx +++ b/docs/src/content/docs/ja/components/infobubble.mdx @@ -7,59 +7,27 @@ head: content: MapConductor iOS SDKのInfoBubble — マーカーに固定されたカスタム情報ウィンドウの表示方法 --- +import InfoBubbleBasicExample from '~/components/components/infobubble/InfoBubbleBasicExample.astro'; +import InfoBubbleInitializer from '~/components/components/infobubble/InfoBubbleInitializer.astro'; +import InfoBubbleStylingExample from '~/components/components/infobubble/InfoBubbleStylingExample.astro'; + `InfoBubble` は `MarkerState` にアンカーされた SwiftUI コンテンツをレンダリングします。 マーカーに可視的で動的な SwiftUI コンテンツが必要な場合にインフォバブルを使用してください。バブルはネイティブプロバイダーのコールアウトではなく、マーカーを追随します。 -```swift -let markerState = MarkerState( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - extra: "Tokyo Station" -) - -GoogleMapView(state: mapViewState) { - Marker(state: markerState) - - InfoBubble(marker: markerState) { - VStack(alignment: .leading, spacing: 4) { - Text(markerState.extra as? String ?? markerState.id) - .font(.headline) - Text("\(markerState.position.latitude), \(markerState.position.longitude)") - .font(.caption) - } - } -} -``` + ## イニシャライザ -```swift -InfoBubble( - marker: MarkerState, - tailOffset: CGPoint = CGPoint(x: 0.5, y: 1.0), - useDefaultStyle: Bool = true, - style: InfoBubbleStyle = .Default, - @ViewBuilder content: () -> Content -) -``` + バブルのコンテンツを駆動するには `extra` またはご自身のモデルを使用してください。 ## スタイリング -デフォルトスタイルはバブルに組み込みのコンテナとテールを付与します。異なる表示が必要な場合は `useDefaultStyle` を設定するか `InfoBubbleStyle` を渡してください。 +デフォルトスタイルはバブルに組み込みのコンテナとテールを付与します。見た目を変えたい場合は `bubbleColor` / `borderColor` / `contentPadding` / `cornerRadius` / `tailSize` を渡してください(Android の `InfoBubble` と同じパラメータです)。テールを含めてすべて自分で描画したい場合は `InfoBubbleCustom` を使います。 -```swift -InfoBubble( - marker: markerState, - tailOffset: CGPoint(x: 0.5, y: 1.0), - useDefaultStyle: true, - style: .Default -) { - Text("Selected place") - .font(.headline) -} -``` + コンテンツは SwiftUI であるため、アプリのモデル、マーカーステート、または選択ステートにバインドできます。 diff --git a/docs/src/content/docs/ja/components/mapviewcomponent.mdx b/docs/src/content/docs/ja/components/mapviewcomponent.mdx index 2bda0fc..9ed4328 100644 --- a/docs/src/content/docs/ja/components/mapviewcomponent.mdx +++ b/docs/src/content/docs/ja/components/mapviewcomponent.mdx @@ -7,6 +7,10 @@ head: content: MapConductor iOS SDKのプロバイダ別SwiftUIマップビュー — Google Maps、Mapbox、MapKit、ArcGIS、MapLibre対応 --- +import MapViewComponentBasicExample from '~/components/components/mapviewcomponent/MapViewComponentBasicExample.astro'; +import MapViewProviderSwitchExample from '~/components/components/mapviewcomponent/MapViewProviderSwitchExample.astro'; +import MapViewLayoutExample from '~/components/components/mapviewcomponent/MapViewLayoutExample.astro'; + SDK はプロバイダー固有の SwiftUI ビューを提供しています。画面で使用するプロバイダーモジュールに合ったビューを選択してください。 | プロバイダー | ビューステート | SwiftUI ビュー | @@ -21,44 +25,7 @@ SDK はプロバイダー固有の SwiftUI ビューを提供しています。 プロバイダーマップビューを、その画面におけるマップオーバーレイのルートコンテナとして使用してください。マップビューはネイティブ SDK ビューの作成と更新を担い、コンテンツクロージャはそこに同期させる MapConductor オーバーレイを記述します。 -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) - ) - - var body: some View { - GoogleMapView( - state: mapViewState, - onMapClick: { point in - print("Map clicked at \(point)") - }, - onCameraMoveEnd: { camera in - print("Camera ended at zoom \(camera.zoom)") - } - ) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(label: "T") - ) - - Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500, - fillColor: UIColor.red.withAlphaComponent(0.2) - ) - } - .ignoresSafeArea() - } -} -``` + ## コンテンツビルダーのアイテム @@ -93,18 +60,7 @@ struct ContentView: View { マップビューの型とステートの型は一致している必要があります。 -```swift -@StateObject private var state = MapboxViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) -) - -MapboxMapView(state: state) { - Marker(position: GeoPoint(latitude: 35.6812, longitude: 139.7671)) -} -``` + `Marker`、`Circle`、`Polyline` などのオーバーレイ宣言は共通です。プロバイダー固有の挙動は、プロバイダーのセットアップ、プロバイダーのデザイン型、または `getMapViewHolder()` を通じたネイティブ SDK へのアクセスに委ねてください。 @@ -112,12 +68,6 @@ MapboxMapView(state: state) { プロバイダービューは通常の SwiftUI ビューです。SwiftUI のレイアウトモディファイアをプロバイダーマップビュー自体に適用してください。 -```swift -GoogleMapView(state: mapViewState) { - Marker(position: tokyoStation) -} -.frame(height: 320) -.clipShape(RoundedRectangle(cornerRadius: 8)) -``` + 時間のかかるデータ読み込み処理はコンテンツクロージャの外部に置いてください。クロージャは現在のオーバーレイ状態を記述するものであり、ネットワークリクエストやコストの高い座標処理を実行する場所ではありません。 diff --git a/docs/src/content/docs/ja/components/mapviewstate.mdx b/docs/src/content/docs/ja/components/mapviewstate.mdx index 908db92..0181d76 100644 --- a/docs/src/content/docs/ja/components/mapviewstate.mdx +++ b/docs/src/content/docs/ja/components/mapviewstate.mdx @@ -7,6 +7,12 @@ head: content: MapConductor iOS SDKのMapViewState — カメラ位置、地図タイプ、スタイルの管理方法 --- +import MapViewStateExample from '~/components/components/mapviewstate/MapViewStateExample.astro'; +import MapViewStatePassExample from '~/components/components/mapviewstate/MapViewStatePassExample.astro'; +import MapViewStateCameraFullExample from '~/components/components/mapviewstate/MapViewStateCameraFullExample.astro'; +import MapViewStateCameraPointExample from '~/components/components/mapviewstate/MapViewStateCameraPointExample.astro'; +import MapViewStateHolderExample from '~/components/components/mapviewstate/MapViewStateHolderExample.astro'; + `MapViewState` はプロバイダー固有のステート型の基底クラスです。アプリケーションコードでは、以下の具体的なステートクラスのいずれかを使用してください。 - `GoogleMapViewState` @@ -26,45 +32,21 @@ head: ビューがマップステートを所有する場合、具体的なプロバイダーステートを SwiftUI の `@StateObject` として使用してください。 -```swift -@StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) -) -``` + 同じオブジェクトを対応するプロバイダーマップビューに渡してください。 -```swift -GoogleMapView(state: mapViewState) { - Marker(position: GeoPoint(latitude: 35.6812, longitude: 139.7671)) -} -``` + ## カメラの制御 ズーム、方角、チルト、パディング、または表示領域を変更する必要がある場合は、完全なカメラ値に移動してください。 -```swift -mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6586, longitude: 139.7454), - zoom: 15 - ), - durationMillis: 500 -) -``` + 現在のカメラ設定をプロバイダー実装によって維持したまま地点だけ移動する場合は、次のようにしてください。 -```swift -mapViewState.moveCameraTo( - position: GeoPoint(latitude: 35.7101, longitude: 139.8107), - durationMillis: 500 -) -``` + 境界のようなエリアに移動するには、アプリ内で中心座標とズームレベルを計算し、`moveCameraTo(cameraPosition:durationMillis:)` を呼び出してください。 @@ -86,9 +68,4 @@ mapViewState.moveCameraTo( ホルダーはネイティブマップの準備が整う前は `nil` になる場合があります。プロバイダー固有のアクセスが必要な時点でのみガードしてキャストしてください。 -```swift -if let holder = mapViewState.getMapViewHolder(), - let nativeMapView = holder.mapView as? GMSMapView { - nativeMapView.isTrafficEnabled = true -} -``` + diff --git a/docs/src/content/docs/ja/components/marker.mdx b/docs/src/content/docs/ja/components/marker.mdx index 9e6ce82..f475f89 100644 --- a/docs/src/content/docs/ja/components/marker.mdx +++ b/docs/src/content/docs/ja/components/marker.mdx @@ -7,39 +7,21 @@ head: content: MapConductor iOS SDKのMarker — カスタムアイコン、ドラッグ、クリックハンドラを持つマーカーの追加方法 --- +import MarkerBasicExample from '~/components/components/marker/MarkerBasicExample.astro'; +import MarkerInitializer from '~/components/components/marker/MarkerInitializer.astro'; +import MarkerInteractionExample from '~/components/components/marker/MarkerInteractionExample.astro'; +import MarkerStateExample from '~/components/components/marker/MarkerStateExample.astro'; +import MarkerForArrayExample from '~/components/components/marker/MarkerForArrayExample.astro'; + `Marker` はマップにマーカーオーバーレイを追加します。プロバイダーマップビューのコンテンツクロージャ内で使用する `MapOverlayItemProtocol` アイテムです。 少数の静的マーカーの場合はイニシャライザのパラメーターを直接使用してください。マーカーが移動する場合、アイコンが変わる場合、ドラッグ可能な場合、またはマップコンテンツクロージャの外部から更新する必要がある場合は `MarkerState` を使用してください。 -```swift -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(label: "T"), - onClick: { state in - print("Clicked marker \(state.id)") - } -) -``` + ## イニシャライザ -```swift -Marker( - position: GeoPoint, - id: String? = nil, - extra: Any? = nil, - icon: (any MarkerIconProtocol)? = nil, - animation: MarkerAnimation? = nil, - clickable: Bool = true, - draggable: Bool = false, - onClick: OnMarkerEventHandler? = nil, - onDragStart: OnMarkerEventHandler? = nil, - onDrag: OnMarkerEventHandler? = nil, - onDragEnd: OnMarkerEventHandler? = nil, - onAnimateStart: OnMarkerEventHandler? = nil, - onAnimateEnd: OnMarkerEventHandler? = nil -) -``` + アプリ固有のデータは `extra` に格納するか、`DefaultMarkerIcon(label:)` などのカスタムアイコンに可視テキストとして埋め込んでください。 @@ -47,35 +29,11 @@ Marker( タップ処理を制御するには `clickable` を設定し、ドラッグイベントを許可するには `draggable` を設定します。イベントハンドラーは現在の `MarkerState` を受け取るため、ステートからマーカーの id、位置、アイコン、extra データを読み取ることができます。 -```swift -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - id: "tokyo-station", - extra: "Tokyo Station", - draggable: true, - onClick: { marker in - print("Clicked \(marker.extra ?? marker.id)") - }, - onDragEnd: { marker in - print("New position: \(marker.position)") - } -) -``` + ## ステートを使った利用方法 -```swift -let markerState = MarkerState( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - extra: "Tokyo Station", - icon: DefaultMarkerIcon(fillColor: .red, label: "T") -) - -Marker(state: markerState) - -markerState.position = GeoPoint(latitude: 35.6895, longitude: 139.6917) -markerState.animate(.Bounce) -``` + ## マーカーアニメーション @@ -90,22 +48,7 @@ markerState.animate(.Bounce) 動的なコレクションには、安定したマーカーステートを作成し `ForArray` でレンダリングしてください。 -```swift -let markers: [MarkerState] = places.map { place in - MarkerState( - position: place.coordinate, - id: place.id, - extra: place.name, - icon: DefaultMarkerIcon(label: place.shortLabel) - ) -} - -GoogleMapView(state: mapViewState) { - ForArray(markers) { marker in - Marker(state: marker) - } -} -``` + 大規模なセットでは安定した id が重要です。SDK がマーカーを再作成することなく既存のマーカーを更新できるようになります。 diff --git a/docs/src/content/docs/ja/components/polygon.mdx b/docs/src/content/docs/ja/components/polygon.mdx index 0386e0c..5fcbd29 100644 --- a/docs/src/content/docs/ja/components/polygon.mdx +++ b/docs/src/content/docs/ja/components/polygon.mdx @@ -7,53 +7,25 @@ head: content: MapConductor iOS SDKのPolygon — 塗りつぶし・枠線・穴あきポリゴンオーバーレイの描画方法 --- +import PolygonBasicExample from '~/components/components/polygon/PolygonBasicExample.astro'; +import PolygonInitializer from '~/components/components/polygon/PolygonInitializer.astro'; +import PolygonStateWithHolesExample from '~/components/components/polygon/PolygonStateWithHolesExample.astro'; +import PolygonHolesExample from '~/components/components/polygon/PolygonHolesExample.astro'; +import PolygonClickExample from '~/components/components/polygon/PolygonClickExample.astro'; + `Polygon` は塗りつぶされた形状を描画します。 行政境界、区画、ジオフェンス、カスタム領域などのエリアにポリゴンを使用してください。最初と最後の点を繰り返す必要はありません。プロバイダーの実装が形状を閉じます。 -```swift -Polygon( - points: [ - GeoPoint(latitude: 35.67, longitude: 139.75), - GeoPoint(latitude: 35.68, longitude: 139.76), - GeoPoint(latitude: 35.66, longitude: 139.77) - ], - strokeColor: .blue, - strokeWidth: 2, - fillColor: UIColor.blue.withAlphaComponent(0.2), - onClick: { event in - print("Clicked at \(event.clicked)") - } -) -``` + ## イニシャライザ -```swift -Polygon( - points: [GeoPointProtocol], - id: String? = nil, - strokeColor: UIColor = .black, - strokeWidth: Double = 1.0, - fillColor: UIColor = .clear, - geodesic: Bool = false, - zIndex: Int = 0, - extra: Any? = nil, - onClick: OnPolygonEventHandler? = nil -) -``` + `PolygonState` はホールもサポートしています。 -```swift -let state = PolygonState( - points: outerRing, - fillColor: UIColor.green.withAlphaComponent(0.25), - holes: [innerRing] -) - -Polygon(state: state) -``` + 透明度には `fillColor` または `strokeColor` のアルファを使用してください。 @@ -61,39 +33,10 @@ Polygon(state: state) 各ホールは外側のポリゴン内部の座標の配列です。 -```swift -let outerRing = [ - GeoPoint(latitude: 35.67, longitude: 139.74), - GeoPoint(latitude: 35.69, longitude: 139.74), - GeoPoint(latitude: 35.69, longitude: 139.77), - GeoPoint(latitude: 35.67, longitude: 139.77), -] - -let innerRing = [ - GeoPoint(latitude: 35.675, longitude: 139.75), - GeoPoint(latitude: 35.685, longitude: 139.75), - GeoPoint(latitude: 35.685, longitude: 139.76), - GeoPoint(latitude: 35.675, longitude: 139.76), -] - -Polygon( - state: PolygonState( - points: outerRing, - fillColor: UIColor.green.withAlphaComponent(0.25), - holes: [innerRing] - ) -) -``` + ## インタラクション 選択可能なエリアには `onClick` を使用してください。 -```swift -Polygon( - points: districtBoundary, - onClick: { event in - print("Polygon clicked at \(event.clicked)") - } -) -``` + diff --git a/docs/src/content/docs/ja/components/polyline.mdx b/docs/src/content/docs/ja/components/polyline.mdx index d8f422e..4f8107d 100644 --- a/docs/src/content/docs/ja/components/polyline.mdx +++ b/docs/src/content/docs/ja/components/polyline.mdx @@ -7,76 +7,34 @@ head: content: MapConductor iOS SDKのPolyline — ストロークや測地線レンダリングを設定できるポリラインの描画方法 --- +import PolylineBasicExample from '~/components/components/polyline/PolylineBasicExample.astro'; +import PolylineInitializerPoints from '~/components/components/polyline/PolylineInitializerPoints.astro'; +import PolylineInitializerBounds from '~/components/components/polyline/PolylineInitializerBounds.astro'; +import PolylineStateExample from '~/components/components/polyline/PolylineStateExample.astro'; +import PolylineBoundsExample from '~/components/components/polyline/PolylineBoundsExample.astro'; +import PolylineClickExample from '~/components/components/polyline/PolylineClickExample.astro'; + `Polyline` は地理的な点のリストを結ぶ線を描画します。 ルート、トレース、境界線、および形状が順序付きの座標リストに従う任意の線データにポリラインを使用してください。 -```swift -let route: [GeoPoint] = [ - GeoPoint(latitude: 35.6812, longitude: 139.7671), - GeoPoint(latitude: 35.6586, longitude: 139.7454) -] - -Polyline( - points: route, - strokeColor: .blue, - strokeWidth: 4, - geodesic: false, - onClick: { event in - print("Clicked at \(event.clicked)") - } -) -``` + ## イニシャライザ -```swift -Polyline( - points: [GeoPointProtocol], - id: String? = nil, - strokeColor: UIColor = .black, - strokeWidth: Double = 1.0, - geodesic: Bool = false, - extra: Any? = nil, - onClick: OnPolylineEventHandler? = nil -) -``` - -```swift -Polyline( - bounds: GeoRectBounds, - id: String? = nil, - strokeColor: UIColor = .black, - strokeWidth: Double = 1.0, - geodesic: Bool = false, - extra: Any? = nil, - onClick: OnPolylineEventHandler? = nil -) -``` - -## ステートの更新 + -```swift -let state = PolylineState(points: route, strokeColor: .blue, strokeWidth: 3) + -Polyline(state: state) +## ステートの更新 -state.points.append(GeoPoint(latitude: 35.7101, longitude: 139.8107)) -state.strokeColor = .red -``` + ## 境界の輪郭 2 番目のイニシャライザは `GeoRectBounds` の周囲を描画します。 -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) - -Polyline(bounds: bounds, strokeColor: .red, strokeWidth: 2) -``` + これは地理的な範囲のデバッグや矩形の選択範囲の表示に便利です。それ自体ではカメラを移動しません。 @@ -84,13 +42,4 @@ Polyline(bounds: bounds, strokeColor: .red, strokeWidth: 2) ユーザーがルートを選択したり線を確認できるようにする場合は `onClick` を使用してください。 -```swift -Polyline( - points: route, - strokeColor: .blue, - strokeWidth: 4, - onClick: { event in - print("Polyline clicked at \(event.clicked)") - } -) -``` + diff --git a/docs/src/content/docs/ja/core/geopoint.mdx b/docs/src/content/docs/ja/core/geopoint.mdx index b98c22b..4514fa3 100644 --- a/docs/src/content/docs/ja/core/geopoint.mdx +++ b/docs/src/content/docs/ja/core/geopoint.mdx @@ -7,11 +7,17 @@ head: content: GeoPoint — MapConductor iOS SDKのすべてのオーバーレイとカメラ位置で使う地理座標型 --- +import GeoPointBasicExample from '~/components/core/geopoint/GeoPointBasicExample.astro'; +import GeoPointNormalizeExample from '~/components/core/geopoint/GeoPointNormalizeExample.astro'; +import GeoPointCoordinateOrderExample from '~/components/core/geopoint/GeoPointCoordinateOrderExample.astro'; +import GeoPointFromLatLongExample from '~/components/core/geopoint/GeoPointFromLatLongExample.astro'; +import GeoPointValidationExample from '~/components/core/geopoint/GeoPointValidationExample.astro'; +import GeoPointSphericalExample from '~/components/core/geopoint/GeoPointSphericalExample.astro'; +import GeoPointUrlValueExample from '~/components/core/geopoint/GeoPointUrlValueExample.astro'; + `GeoPoint` は SDK の緯度・経度・高度を表す値型です。 -```swift -let point = GeoPoint(latitude: 35.6812, longitude: 139.7671) -``` + ## パブリック API @@ -31,53 +37,30 @@ let point = GeoPoint(latitude: 35.6812, longitude: 139.7671) `GeoPoint` は MapConductor が地理座標を必要とする場所(カメラ位置、マーカー位置、シェイプの頂点、ヒートマップの点、クリックイベントの座標など)であればどこでも使用できます。 -```swift -let raw = GeoPoint(latitude: 95, longitude: 190) -let normalized = raw.normalize() -let valid = normalized.isValid() -let urlValue = normalized.toUrlValue() -``` + ## 座標の順序 メインのイニシャライザーは緯度を先、経度を後に受け取ります: -```swift -let tokyo = GeoPoint(latitude: 35.6812, longitude: 139.7671) -``` + 緯度・経度を明示的に名前付きで扱うコードを移植する場合は `fromLatLong(latitude:longitude:)` を使用してください。多くの GeoJSON 系フォーマットのように経度優先で座標を保持するデータソースを移植する場合は `fromLongLat(longitude:latitude:)` を使用してください。 -```swift -let fromLatLong = GeoPoint.fromLatLong(latitude: 35.6812, longitude: 139.7671) -let fromLongLat = GeoPoint.fromLongLat(longitude: 139.7671, latitude: 35.6812) -``` + ## バリデーションとラッピング `isValid()` は緯度・経度が有効な地理的範囲内にあるかどうかを確認します。`normalize()` と `wrap()` は、ユーザー入力や計算によって通常の経度範囲外の座標が生成された場合に役立ちます。 -```swift -let input = GeoPoint(latitude: 35.6812, longitude: 181.5) - -if input.isValid() { - Marker(position: input) -} else { - Marker(position: input.normalize()) -} -``` + 距離や方位のヘルパーは `GeoPoint` 自体ではなく `Spherical` に用意されています: -```swift -let distanceMeters = Spherical.computeDistanceBetween(from: pointA, to: pointB) -let headingDegrees = Spherical.computeHeading(from: pointA, to: pointB) -``` + ## URL 値 URL、キャッシュキー、デバッグ出力においてコンパクトな座標文字列が必要な場合は `toUrlValue(precision:)` を使用してください: -```swift -let value = tokyo.toUrlValue(precision: 5) -``` + diff --git a/docs/src/content/docs/ja/core/georectbounds.mdx b/docs/src/content/docs/ja/core/georectbounds.mdx index 568cbc1..7ae49e3 100644 --- a/docs/src/content/docs/ja/core/georectbounds.mdx +++ b/docs/src/content/docs/ja/core/georectbounds.mdx @@ -7,14 +7,15 @@ head: content: GeoRectBounds — MapConductor iOS SDKでオーバーレイやカメラ境界を定義する地理矩形範囲型 --- +import GeoRectBoundsBasicExample from '~/components/core/georectbounds/GeoRectBoundsBasicExample.astro'; +import GeoRectBoundsFromPointsExample from '~/components/core/georectbounds/GeoRectBoundsFromPointsExample.astro'; +import GeoRectBoundsFitBoundsExample from '~/components/core/georectbounds/GeoRectBoundsFitBoundsExample.astro'; +import GeoRectBoundsContainmentExample from '~/components/core/georectbounds/GeoRectBoundsContainmentExample.astro'; +import GeoRectBoundsPolylineExample from '~/components/core/georectbounds/GeoRectBoundsPolylineExample.astro'; + `GeoRectBounds` は南西・北東のコーナーをオプションとして保持し、点を追加して拡張できます。 -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) -``` + ## パブリック API @@ -34,46 +35,24 @@ let bounds = GeoRectBounds( ## 点からバウンズを構築する -```swift -let bounds = GeoRectBounds() -for point in points { - bounds.extend(point: point) -} + + +バウンズが見えるようにカメラを移動するには、`fitBounds(bounds:padding:)` を使用してください: -if let center = bounds.center { - mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition(position: center, zoom: 12), - durationMillis: 300 - ) -} -``` + -バウンズ領域が見えるようにマップを移動するには、目的のカメラ位置を自分で計算し、`moveCameraTo(cameraPosition:durationMillis:)` を呼び出してください。 +ズームレベルを細かく制御したい場合は、目的のカメラ位置を自分で計算し、`moveCameraTo(cameraPosition:durationMillis:)` を呼び出してください。 ## 包含と交差判定 バウンズは表示中のデータのフィルタリング、地域間の重なりの検出、デバッグオーバーレイの構築などに役立ちます。 -```swift -if bounds.contains(point: candidate) { - visiblePoints.append(candidate) -} - -if searchArea.intersects(other: loadedTileBounds) { - loadMoreData() -} -``` + ## バウンズの表示 矩形バウンズの輪郭を表示するには、バウンズを `Polyline` に渡します: -```swift -Polyline( - bounds: bounds, - strokeColor: .red, - strokeWidth: 2 -) -``` + 既存のバウンズを単純な度数ベースのマージンで拡張する場合は `expandedByDegrees(latPad:lonPad:)` を使用してください。これはカメラフィッティングの代替手段ではなく、より大きな地理的矩形を返すだけです。 diff --git a/docs/src/content/docs/ja/core/mapcameraposition.mdx b/docs/src/content/docs/ja/core/mapcameraposition.mdx index af287d3..e6aeba1 100644 --- a/docs/src/content/docs/ja/core/mapcameraposition.mdx +++ b/docs/src/content/docs/ja/core/mapcameraposition.mdx @@ -7,29 +7,20 @@ head: content: MapCameraPosition — MapConductor iOS SDKでマップの中心・ズーム・チルト・方向を設定する方法 --- +import MapCameraPositionBasicExample from '~/components/core/mapcameraposition/MapCameraPositionBasicExample.astro'; +import MapCameraPositionInitializerExample from '~/components/core/mapcameraposition/MapCameraPositionInitializerExample.astro'; +import MoveCameraExample from '~/components/core/mapcameraposition/MoveCameraExample.astro'; +import CopyCameraExample from '~/components/core/mapcameraposition/CopyCameraExample.astro'; +import FitBoundsExample from '~/components/core/mapcameraposition/FitBoundsExample.astro'; +import CameraEventsExample from '~/components/core/mapcameraposition/CameraEventsExample.astro'; + `MapCameraPosition` はマップカメラを表します。 -```swift -let camera = MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13, - bearing: 0, - tilt: 0 -) -``` + ## イニシャライザー -```swift -MapCameraPosition( - position: GeoPointProtocol, - zoom: Double = 0.0, - bearing: Double = 0.0, - tilt: Double = 0.0, - paddings: MapPaddingsProtocol? = MapPaddings.Zeros, - visibleRegion: VisibleRegion? = nil -) -``` + プロパティ名は `target` ではなく `position` です。 @@ -46,47 +37,24 @@ MapCameraPosition( ## カメラの移動 -```swift -mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6586, longitude: 139.7454), - zoom: 15 - ), - durationMillis: 500 -) - -mapViewState.moveCameraTo( - position: GeoPoint(latitude: 35.7101, longitude: 139.8107), - durationMillis: 500 -) -``` + 現在のカメラから新しい値を派生させるには `copy(...)` を使用してください: -```swift -let next = mapViewState.cameraPosition.copy(zoom: 16) -mapViewState.moveCameraTo(cameraPosition: next, durationMillis: 300) -``` + + +## バウンズへのフィット + +`fitBounds(bounds:padding:)` を使うと、`GeoRectBounds` が画面内に収まるようにカメラを移動・ズームできます: + + + +`padding` パラメーターは全辺にスクリーンスペースのインセット(ポイント単位)を追加します。ズームレベルの計算は各プロバイダーがネイティブに行うため、プロバイダー間で結果が若干異なる場合があります。 ## カメライベントへの対応 プロバイダーのマップビューはイニシャライザーのパラメーターを通じてカメラの変化を通知できます: -```swift -GoogleMapView( - state: mapViewState, - onCameraMoveStart: { camera in - print("Started at \(camera.position)") - }, - onCameraMove: { camera in - print("Moving at zoom \(camera.zoom)") - }, - onCameraMoveEnd: { camera in - print("Ended at \(camera.position)") - } -) { - Marker(position: mapViewState.cameraPosition.position) -} -``` + カメライベントは UI の更新、表示データの読み込み、アナリティクスなどに活用してください。`onCameraMove` では重い処理を避け、コストのかかる更新は `onCameraMoveEnd` で行うことを推奨します。 diff --git a/docs/src/content/docs/ja/core/marker-icons.mdx b/docs/src/content/docs/ja/core/marker-icons.mdx index 9ae53fb..5f289a2 100644 --- a/docs/src/content/docs/ja/core/marker-icons.mdx +++ b/docs/src/content/docs/ja/core/marker-icons.mdx @@ -7,6 +7,11 @@ head: content: MapConductor iOS SDKのマーカーアイコン種別 — DefaultMarkerIconとカスタムアイコンインターフェース --- +import DefaultMarkerIconExample from '~/components/core/marker-icons/DefaultMarkerIconExample.astro'; +import ImageIconExample from '~/components/core/marker-icons/ImageIconExample.astro'; +import ImageDefaultIconExample from '~/components/core/marker-icons/ImageDefaultIconExample.astro'; +import BitmapIconExample from '~/components/core/marker-icons/BitmapIconExample.astro'; + マーカーアイコンは `MarkerIconProtocol` に準拠しています。 マーカーアイコンを使って `Marker` および `MarkerState` の見た目を制御します。アイコンオブジェクトはマーカーステートに保存され、ネイティブプロバイダーのマーカーと同期されます。 @@ -15,18 +20,7 @@ head: `DefaultMarkerIcon` は SDK のデフォルトピンを描画します。プロバイダー間で一貫したマーカーの見た目を手軽に実現できます。 -```swift -let icon = DefaultMarkerIcon( - fillColor: .red, - strokeColor: .white, - label: "A" -) - -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: icon -) -``` + 利用可能なイニシャライザーのパラメーターには `fillColor`、`strokeColor`、`strokeWidth`、`scale`、`label`、`labelTextColor`、`labelTextSize`、`labelTypeFace`、`labelStrokeColor`、`infoAnchor`、`iconSize`、`debug` があります。 @@ -36,37 +30,19 @@ Marker( `ImageIcon` は `UIImage` を直接使用します。アプリにマーカー用の画像アセットが既にある場合に使用してください。 -```swift -let icon = ImageIcon( - image: UIImage(named: "custom-marker")!, - iconSize: 48, - anchor: CGPoint(x: 0.5, y: 1.0) -) -``` + ## ImageDefaultIcon `ImageDefaultIcon` は画像をデフォルトのピン形状にクリップします。 -```swift -let icon = ImageDefaultIcon( - backgroundImage: UIImage(named: "avatar")!, - strokeColor: .white, - label: "1" -) -``` + ## BitmapIcon `BitmapIcon` はレンダリングされた `UIImage` をラップします。SwiftUI ビューやその他のカスタム描画を画像としてレンダリングしてからマーカーシステムに渡す場合に使用してください。 -```swift -let icon = BitmapIcon( - bitmap: image, - anchor: CGPoint(x: 0.5, y: 1.0), - scale: 1.0 -) -``` + ## アイコンタイプの選び方 diff --git a/docs/src/content/docs/ja/core/spherical-utilities.mdx b/docs/src/content/docs/ja/core/spherical-utilities.mdx index 31effd0..df0050a 100644 --- a/docs/src/content/docs/ja/core/spherical-utilities.mdx +++ b/docs/src/content/docs/ja/core/spherical-utilities.mdx @@ -7,6 +7,12 @@ head: content: MapConductor iOS SDKの球面幾何学ユーティリティ — 座標間の距離・方位・面積の計算 --- +import SphericalBasicExample from '~/components/core/spherical-utilities/SphericalBasicExample.astro'; +import SphericalLengthAreaExample from '~/components/core/spherical-utilities/SphericalLengthAreaExample.astro'; +import SphericalRouteLengthExample from '~/components/core/spherical-utilities/SphericalRouteLengthExample.astro'; +import SphericalAreaExample from '~/components/core/spherical-utilities/SphericalAreaExample.astro'; +import SphericalInterpolateExample from '~/components/core/spherical-utilities/SphericalInterpolateExample.astro'; + 球面ジオメトリのヘルパーは `Spherical` enum を通じて提供されています。プロバイダー SDK の外でマップ関連のジオメトリが必要な場合に、計測や派生座標の計算にこれらのヘルパーを使用してください。 ## パブリック関数 @@ -18,30 +24,18 @@ head: - `computeLength(_:) -> Double` - `computeArea(_:) -> Double` - `computeSignedArea(_:) -> Double` -- `sphericalInterpolate(from:to:fraction:) -> GeoPoint` +- `interpolate(from:to:fraction:) -> GeoPoint` - `linearInterpolate(from:to:fraction:) -> GeoPoint` -```swift -let tokyo = GeoPoint(latitude: 35.6812, longitude: 139.7671) -let tower = GeoPoint(latitude: 35.6586, longitude: 139.7454) - -let distance = Spherical.computeDistanceBetween(from: tokyo, to: tower) -let heading = Spherical.computeHeading(from: tokyo, to: tower) -let point = Spherical.computeOffset(origin: tokyo, distance: 1_000, heading: 90) -``` + -```swift -let routeLength = Spherical.computeLength(route) -let polygonArea = Spherical.computeArea(polygon) -``` + ## ルートの長さ ルートやトラックには `computeLength(_:)` を使用してください: -```swift -let routeLengthMeters = Spherical.computeLength(routePoints) -``` + 結果はメートル単位で返されます。 @@ -49,19 +43,10 @@ let routeLengthMeters = Spherical.computeLength(routePoints) 閉じたパスの符号なし面積には `computeArea(_:)` を、巻き方向が重要な場合は `computeSignedArea(_:)` を使用してください: -```swift -let areaSquareMeters = Spherical.computeArea(polygonPoints) -let signedArea = Spherical.computeSignedArea(polygonPoints) -``` + ## 補間 -球面上の移動には `sphericalInterpolate(from:to:fraction:)` を、単純な緯度・経度の線形補間には `linearInterpolate(from:to:fraction:)` を使用してください。 +球面上の移動には `interpolate(from:to:fraction:)` を、単純な緯度・経度の線形補間には `linearInterpolate(from:to:fraction:)` を使用してください。 -```swift -let halfway = Spherical.sphericalInterpolate( - from: pointA, - to: pointB, - fraction: 0.5 -) -``` + diff --git a/docs/src/content/docs/ja/core/zoom-levels.mdx b/docs/src/content/docs/ja/core/zoom-levels.mdx index 28df5f5..281cd92 100644 --- a/docs/src/content/docs/ja/core/zoom-levels.mdx +++ b/docs/src/content/docs/ja/core/zoom-levels.mdx @@ -7,45 +7,20 @@ head: content: ズームレベル解説 — MapConductor iOS SDKのMapCameraPositionでのズーム値の使い方 --- +import ZoomLevelBasicExample from '~/components/core/zoom-levels/ZoomLevelBasicExample.astro'; +import ZoomControlsExample from '~/components/core/zoom-levels/ZoomControlsExample.astro'; + ズームは `MapCameraPosition.zoom` で表されます。 ズーム値は選択されたプロバイダー SDK にそのまま渡されます。同じ数値は一般的に期待通りの「近づく」または「遠ざかる」を意味しますが、正確なマップスケールとプロバイダーの制限はプロバイダーごとに異なります。 -```swift -let camera = MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 14 -) - -mapViewState.moveCameraTo(cameraPosition: camera, durationMillis: 300) -``` + アプリでズームの制限が必要な場合は、カメラを移動する前に値をクランプしてください: ## ズームコントロール -```swift -let minZoom = 3.0 -let maxZoom = 18.0 - -func zoomIn() { - let current = mapViewState.cameraPosition - let nextZoom = min(maxZoom, current.zoom + 1) - mapViewState.moveCameraTo( - cameraPosition: current.copy(zoom: nextZoom), - durationMillis: 200 - ) -} - -func zoomOut() { - let current = mapViewState.cameraPosition - let nextZoom = max(minZoom, current.zoom - 1) - mapViewState.moveCameraTo( - cameraPosition: current.copy(zoom: nextZoom), - durationMillis: 200 - ) -} -``` + ズームの変化を監視するには、マップビューのイニシャライザー引数 `onCameraMove` または `onCameraMoveEnd` を使用してください。 diff --git a/docs/src/content/docs/ja/event/event-handlers.mdx b/docs/src/content/docs/ja/event/event-handlers.mdx index 83aa691..574a085 100644 --- a/docs/src/content/docs/ja/event/event-handlers.mdx +++ b/docs/src/content/docs/ja/event/event-handlers.mdx @@ -7,35 +7,13 @@ head: content: MapConductor iOS SDKのイベントハンドラ — onMapClick、onMarkerClick、onDragEndなどのマップ・オーバーレイイベント --- +import EventHandlersMapExample from '~/components/event/EventHandlersMapExample.astro'; +import EventHandlersMarkerExample from '~/components/event/EventHandlersMarkerExample.astro'; +import EventHandlersShapeExample from '~/components/event/EventHandlersShapeExample.astro'; + マップイベントハンドラーは、各プロバイダー固有のマップビューのイニシャライザーに渡します。SwiftUI のビューモディファイアではありません。 -```swift -GoogleMapView( - state: mapViewState, - onMapLoaded: { state in - print("Loaded: \(state.id)") - }, - onMapClick: { point in - print("Clicked: \(point.latitude), \(point.longitude)") - }, - onCameraMoveStart: { camera in - print("Move started: \(camera.zoom)") - }, - onCameraMove: { camera in - print("Moving: \(camera.position)") - }, - onCameraMoveEnd: { camera in - print("Move ended: \(camera.zoom)") - } -) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - onClick: { marker in - print("Marker clicked: \(marker.id)") - } - ) -} -``` + 同じイニシャライザー引数が `GoogleMapView`、`MapboxMapView`、`MapKitMapView`、`MapLibreMapView`、`ArcGISMapView` で利用できます。 @@ -50,45 +28,12 @@ GoogleMapView( - `onAnimateStart: (MarkerState) -> Void` - `onAnimateEnd: (MarkerState) -> Void` -```swift -Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - draggable: true, - onClick: { state in - print("Clicked marker \(state.id)") - }, - onDragEnd: { state in - print("Dropped at \(state.position)") - } -) -``` + ## シェイプイベント シェイプのクリックハンドラーはオーバーレイまたはステートのイニシャライザーで設定します: -```swift -Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500, - onClick: { event in - print("Circle clicked at \(event.clicked)") - } -) - -Polyline( - points: route, - onClick: { event in - print("Polyline clicked at \(event.clicked)") - } -) - -Polygon( - points: polygon, - onClick: { event in - print("Polygon clicked at \(event.clicked)") - } -) -``` + `GroundImage` にも `onClick: (GroundImageEvent) -> Void` があります。`RasterLayerState` は `RasterLayerEvent` 型を定義していますが、`RasterLayer` 自体は `onClick` イニシャライザーを公開していません。 diff --git a/docs/src/content/docs/ja/experimental/geojson-layer.mdx b/docs/src/content/docs/ja/experimental/geojson-layer.mdx new file mode 100644 index 0000000..997735d --- /dev/null +++ b/docs/src/content/docs/ja/experimental/geojson-layer.mdx @@ -0,0 +1,65 @@ +--- +title: GeoJSON レイヤー +head: + - tag: meta + attrs: + name: description + content: MapConductorGeoJSON を使って GeoJSON データをタイルベースのマップレイヤーとして描画する +--- + +import GeoJSONPackageInstall from '~/components/experimental/geojson-layer/GeoJSONPackageInstall.astro'; +import GeoJSONProductDep from '~/components/experimental/geojson-layer/GeoJSONProductDep.astro'; +import GeoJSONBasicExample from '~/components/experimental/geojson-layer/GeoJSONBasicExample.astro'; +import GeoJSONLayerStateInit from '~/components/experimental/geojson-layer/GeoJSONLayerStateInit.astro'; +import GeoJSONLayerStateUpdate from '~/components/experimental/geojson-layer/GeoJSONLayerStateUpdate.astro'; +import GeoJSONLayerStyle from '~/components/experimental/geojson-layer/GeoJSONLayerStyle.astro'; +import GeoJSONLoadFeatures from '~/components/experimental/geojson-layer/GeoJSONLoadFeatures.astro'; +import GeoJSONFeatureState from '~/components/experimental/geojson-layer/GeoJSONFeatureState.astro'; +import GeoJSONClickHandler from '~/components/experimental/geojson-layer/GeoJSONClickHandler.astro'; +import GeoJSONClickHandlerMap from '~/components/experimental/geojson-layer/GeoJSONClickHandlerMap.astro'; + +`MapConductorGeoJSON` は GeoJSON フィーチャコレクションをタイルベースのラスターレイヤーとして描画します。標準の `RasterLayer` 統合を通じてすべてのマッププロバイダーで動作します。 + +## パッケージ + + + +ターゲットにプロダクトを追加します: + + + +## 基本的な使い方 + + + +## GeoJSONLayerState + +`GeoJSONLayerState` はタイルサーバーとレンダリングパイプラインを管理します。 + + + +プロパティは実行時に更新できます: + + + +## LayerStyle + + + +## フィーチャの読み込み + +`[GeoJSONFeature]` 配列を `GeoJSONLayer` に直接渡します。フィーチャリストが変更されるとタイルが再レンダリングされます。 + + + +フィーチャごとの表示切り替えが必要な場合は `GeoJSONFeatureState` を使用します: + + + +## クリック処理 + + + +マップの `onMapClick` ハンドラーから `layerState.processClick(geoPoint:)` を呼び出してタップを GeoJSON ヒットテストに転送します: + + diff --git a/docs/src/content/docs/ja/experimental/heatmap.mdx b/docs/src/content/docs/ja/experimental/heatmap.mdx index b37bc1d..f5eaf3d 100644 --- a/docs/src/content/docs/ja/experimental/heatmap.mdx +++ b/docs/src/content/docs/ja/experimental/heatmap.mdx @@ -7,6 +7,13 @@ head: content: MapConductor iOS SDKのヒートマップオーバーレイ — 任意の地図プロバイダで動作するタイルベースの可視化 --- +import HeatmapPackageInstall from '~/components/experimental/heatmap/HeatmapPackageInstall.astro'; +import HeatmapProductDep from '~/components/experimental/heatmap/HeatmapProductDep.astro'; +import HeatmapBasicExample from '~/components/experimental/heatmap/HeatmapBasicExample.astro'; +import HeatmapBatchPoints from '~/components/experimental/heatmap/HeatmapBatchPoints.astro'; +import HeatmapDynamicState from '~/components/experimental/heatmap/HeatmapDynamicState.astro'; +import HeatmapCustomGradient from '~/components/experimental/heatmap/HeatmapCustomGradient.astro'; + `MapConductorHeatmap` モジュールは、マップ実装に依存しないヒートマップオーバーレイを提供します。ヒートマップデータをラスターレイヤーとしてレンダリングし、あらゆるマッププロバイダーで動作します。 各地点に数値ウェイトがあり、個々のマーカーを確認するのではなく、エリア全体の密度や強度を把握したい場合にヒートマップを使用してください。 @@ -15,53 +22,23 @@ head: Package.swift に以下を追加してください。 -```swift -.package(url: "https://github.com/MapConductor/ios-heatmap", from: "{HEATMAP_MODULE_VERSION}"), -``` + ターゲットの依存関係に以下を追加してください。 -```swift -.product(name: "MapConductorHeatmap", package: "ios-heatmap"), -``` + ## 基本的な使い方 少数の地点、またはオーバーレイ内の SwiftUI コンテンツとして自然に宣言できる場合は `HeatmapPointView` を使用してください。 -```swift -import MapConductorHeatmap - -GoogleMapView(state: mapViewState) { - HeatmapOverlay( - radiusPx: 20, - opacity: 0.7 - ) { - HeatmapPointView( - position: GeoPoint(latitude: 35.6762, longitude: 139.6503), - weight: 1.0 - ) - HeatmapPointView( - position: GeoPoint(latitude: 35.6895, longitude: 139.6917), - weight: 2.5 - ) - } -} -``` + ## バッチ地点 大規模なデータセットには `HeatmapPoints` を使用して、すべての地点を一度の更新で追加してください。これにより、個別の地点の追加・削除更新が多数発生することを避けられます。 -```swift -let pointStates: [HeatmapPointState] = [...] - -GoogleMapView(state: mapViewState) { - HeatmapOverlay(radiusPx: 20) { - HeatmapPoints(pointStates) - } -} -``` + `HeatmapPoint` はレンダラーが使用する内部データ値です。マップコンテンツでは `HeatmapPointView` または `HeatmapPoints` を使用してください。 @@ -69,51 +46,11 @@ GoogleMapView(state: mapViewState) { 実行時にヒートマップのプロパティを制御するには `HeatmapOverlayState` を使用してください。 -```swift -struct DynamicHeatmapMap: View { - @StateObject private var heatmapState = HeatmapOverlayState( - radiusPx: 20, - opacity: 0.7 - ) - @StateObject private var mapViewState = GoogleMapViewState() - - var body: some View { - VStack { - GoogleMapView(state: mapViewState) { - HeatmapOverlay(state: heatmapState) { - HeatmapPoints(pointStates) - } - } - - HStack { - Text("Radius:") - Slider(value: .init( - get: { Double(heatmapState.radiusPx) }, - set: { heatmapState.radiusPx = Int($0) } - ), in: 10...50) - - Text("Opacity:") - Slider(value: $heatmapState.opacity, in: 0...1) - } - .padding() - } - } -} -``` + ## カスタムグラデーション -```swift -let gradient = HeatmapGradient( - stops: [ - HeatmapGradientStop(position: 0.0, color: UIColor(red: 0, green: 0, blue: 1, alpha: 1)), // 青 - HeatmapGradientStop(position: 0.5, color: UIColor(red: 0, green: 1, blue: 0, alpha: 1)), // 緑 - HeatmapGradientStop(position: 1.0, color: UIColor(red: 1, green: 0, blue: 0, alpha: 1)) // 赤 - ] -) - -let heatmapState = HeatmapOverlayState(gradient: gradient) -``` + `HeatmapOverlayState` はイニシャライザを通じて `maxIntensity`、`weightProvider`、`tileSize`、`trackPointUpdates`、`disableTileServerCache` もサポートしています。一貫した強度スケーリング、カスタムウェイト付け、または頻繁に変化するデータセットのタイルキャッシュ動作が必要な場合に使用してください。 diff --git a/docs/src/content/docs/ja/experimental/marker-clustering.mdx b/docs/src/content/docs/ja/experimental/marker-clustering.mdx index f240065..4062475 100644 --- a/docs/src/content/docs/ja/experimental/marker-clustering.mdx +++ b/docs/src/content/docs/ja/experimental/marker-clustering.mdx @@ -7,51 +7,25 @@ head: content: MapConductor iOS SDKの自動マーカークラスタリング — 近接マーカーをクラスターにグループ化 --- +import MarkerClusterPackageInstall from '~/components/experimental/marker-clustering/MarkerClusterPackageInstall.astro'; +import MarkerClusterProductDep from '~/components/experimental/marker-clustering/MarkerClusterProductDep.astro'; +import MarkerClusterBasicExample from '~/components/experimental/marker-clustering/MarkerClusterBasicExample.astro'; +import MarkerClusterOnClick from '~/components/experimental/marker-clustering/MarkerClusterOnClick.astro'; +import MarkerClusterState from '~/components/experimental/marker-clustering/MarkerClusterState.astro'; + マーカークラスタリングのパッケージプロダクト名およびモジュール名は `MapConductorMarkerCluster` です。 画面に多数のマーカーを同時に表示でき、個々のピンが重なったり更新コストが高くなる場合にマーカークラスタリングを使用してください。このモジュールは現在のカメラとビューポートに基づき、近くにあるソースマーカーのグループをクラスターマーカーに置き換えます。 -```swift -.package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "{MARKER_CLUSTERING_MODULE_VERSION}") -``` + -```swift -.product(name: "MapConductorMarkerCluster", package: "ios-marker-cluster") -``` + ## 基本的な使い方 `MarkerClusterGroup` はプロバイダーの実際のマーカー型に対してジェネリックです。プロバイダーモジュールの型エイリアスを使用してください。 -```swift -import MapConductorCore -import MapConductorForGoogleMaps -import MapConductorMarkerCluster - -struct ClusterMap: View { - @StateObject private var mapViewState = GoogleMapViewState() - - @State private var clusterStrategy = - MarkerClusterStrategy( - clusterRadiusPx: 60, - minClusterSize: 2, - enableZoomAnimation: true, - enablePanAnimation: true - ) - - let markers: [MarkerState] - - var body: some View { - GoogleMapView(state: mapViewState) { - MarkerClusterGroup(strategy: clusterStrategy) { - ForArray(markers) { marker in - Marker(state: marker) - } - } - } - } -} -``` + `MarkerClusterGroup` はコンテンツクロージャから `MarkerState` の値を取り出してレンダリングストラテジーを適用し、他のオーバーレイコンテンツはそのまま通過させます。クラスタリングが更新をまたいでソースマーカーを追跡できるよう、マーカーの id を安定させてください。 @@ -83,41 +57,12 @@ struct ClusterMap: View { ユーザーがクラスターをタップした際に応答するには `onClusterClick` を使用してください。 -```swift -MarkerClusterGroup( - onClusterClick: { cluster in - print("Cluster clicked: \(cluster.count)") - } -) { - ForArray(markers) { marker in - Marker(state: marker) - } -} -``` + ## クラスターステート -クラスタリングの設定を監視可能にしたい場合やデバッグ用の半径サークルが必要な場合は `MarkerClusterGroupState` を使用してください。 - -```swift -@StateObject private var clusterState = - MarkerClusterGroupState( - clusterRadiusPx: 60, - minClusterSize: 2 - ) - -MarkerClusterGroup(state: clusterState) { - ForArray(markers) { marker in - Marker(state: marker) - } -} -``` - -`MarkerClusterGroupState` はデバッグ用の半径表示プロパティを公開しています。 +クラスタリングの設定を監視可能にしたい場合は `MarkerClusterGroupState` を使用してください。 -- `showClusterRadiusCircle` -- `clusterRadiusStrokeColor` -- `clusterRadiusStrokeWidth` -- `clusterRadiusFillColor` + -これらは `clusterRadiusPx` と `expandMargin` を調整する際に役立ちます。 +`debugHullPolygons` は各クラスタの凸包を描画します。`clusterRadiusPx` と `expandMargin` を調整する際に役立ちます。クラスタリング API が公開するデバッグ用スイッチはこれだけで、Android・iOS・React で共通です。 diff --git a/docs/src/content/docs/ja/get-started.mdx b/docs/src/content/docs/ja/get-started.mdx index 32a0f7f..372aed3 100644 --- a/docs/src/content/docs/ja/get-started.mdx +++ b/docs/src/content/docs/ja/get-started.mdx @@ -7,7 +7,13 @@ head: content: MapConductor iOS SDKのクイックスタート — 最短でマップビューとマーカーを表示するチュートリアル --- -このチュートリアルでは、現在の iOS ソースに存在する API を使用した小さな SwiftUI マップ画面を紹介します。具体性のために Google Maps から始め、他のマップ SDK を使用する場合に切り替えるプロバイダーの型を説明します。 +import GetStartedPackagesExample from '~/components/get-started/GetStartedPackagesExample.astro'; +import GetStartedProductsExample from '~/components/get-started/GetStartedProductsExample.astro'; +import GetStartedDisplayMapExample from '~/components/get-started/GetStartedDisplayMapExample.astro'; +import GetStartedHandleInteractionsExample from '~/components/get-started/GetStartedHandleInteractionsExample.astro'; +import GetStartedMoveCameraExample from '~/components/get-started/GetStartedMoveCameraExample.astro'; + +このチュートリアルでは、現在の iOS ソースに存在する API を使用した小さな SwiftUI マップ画面を紹介します。MapKit は iOS の OS 標準フレームワークであり、API キーなどのネイティブセットアップが不要なため、具体性のために MapKit から始め、他のマップ SDK を使用する場合に切り替えるプロバイダーの型を説明します。 ## 作成するもの @@ -20,92 +26,29 @@ head: - Swift Package Manager をサポートした Xcode - SwiftUI iOS アプリケーション -- 選択したプロバイダーのネイティブセットアップ(Google Maps API キーや Mapbox アクセストークンなど) +- 選択したプロバイダーのネイティブセットアップ(Google Maps API キーや Mapbox アクセストークンなど。MapKit では不要) ## パッケージの追加 コアパッケージと 1 つのプロバイダーパッケージを追加します: -```swift -.package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), -.package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}") -``` + 次にターゲットにプロダクトを追加します: -```swift -.product(name: "MapConductorCore", package: "ios-sdk-core"), -.product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps") -``` + 使用するプロバイダーモジュールのみ追加してください。`MapConductorHeatmap` や `MapConductorMarkerCluster` などのオプションモジュールは、画面でそれらのオーバーレイが必要になったときに後から追加できます。 -## Google Maps の初期化 - -```swift -import GoogleMaps +## ネイティブセットアップは不要 -@main -struct ExampleApp: App { - init() { - GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` +MapKit は OS 標準フレームワークなので、API キーやアクセストークン、SDK の初期化は必要ありません。そのままマップの表示に進めます。 他のプロバイダーの場合は、マップを表示する前にそのプロバイダーが要求するネイティブ SDK のセットアップを行ってください。MapConductor はプロバイダーの API キー、アクセストークン、ライセンス、タイル設定を置き換えるものではありません。 ## マップの表示 -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) - ) - - var body: some View { - GoogleMapView( - state: mapViewState, - onMapClick: { point in - print("Map clicked at \(point)") - } - ) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(fillColor: .red, label: "T"), - onClick: { marker in - print("Marker clicked: \(marker.id)") - } - ) - - Circle( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 1_000, - fillColor: UIColor.blue.withAlphaComponent(0.2) - ) - - Polyline(points: [ - GeoPoint(latitude: 35.6812, longitude: 139.7671), - GeoPoint(latitude: 35.6586, longitude: 139.7454) - ]) - } - .ignoresSafeArea() - } -} -``` + マップビューはネイティブマップインスタンスを所有します。`@StateObject` は現在のカメラポジションや選択したマップデザインなど、プロバイダー横断のマップ状態を管理します。 @@ -115,45 +58,13 @@ struct ContentView: View { マップイベントはプロバイダーのマップビューのイニシャライザパラメーターとして渡します: -```swift -GoogleMapView( - state: mapViewState, - onMapLoaded: { state in - print("Loaded map \(state.id)") - }, - onMapClick: { point in - print("Tapped \(point.latitude), \(point.longitude)") - }, - onCameraMoveEnd: { camera in - print("Camera ended at zoom \(camera.zoom)") - } -) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - draggable: true, - onClick: { marker in - print("Marker clicked: \(marker.id)") - }, - onDragEnd: { marker in - print("Marker dropped at \(marker.position)") - } - ) -} -``` - -これらは SwiftUI モディファイアではありません。`GoogleMapView`、`MapboxMapView`、`MapKitMapView`、`MapLibreMapView`、または `ArcGISMapView` のイニシャライザパラメーターを直接使用してください。 + + +これらは SwiftUI モディファイアではありません。`MapKitMapView`、`GoogleMapView`、`MapboxMapView`、`MapLibreMapView`、または `ArcGISMapView` のイニシャライザパラメーターを直接使用してください。 ## カメラの移動 -```swift -mapViewState.moveCameraTo( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6586, longitude: 139.7454), - zoom: 15 - ), - durationMillis: 500 -) -``` + 複数のポイントをフレームに収める必要がある場合は、独自のコードでカメラの中心とズームを計算し、`moveCameraTo(cameraPosition:durationMillis:)` を呼び出してください。 diff --git a/docs/src/content/docs/ja/index.mdx b/docs/src/content/docs/ja/index.mdx index 2d766a0..722bb6d 100644 --- a/docs/src/content/docs/ja/index.mdx +++ b/docs/src/content/docs/ja/index.mdx @@ -14,6 +14,18 @@ import { CardGrid, Card } from "@astrojs/starlight/components"; import Features from "~/components/index/Features.astro"; import MapConductorIntroduction from "~/components/index/MapConductorIntroduction.astro"; import TopCards from "~/components/index/TopCards.astro"; +import { Image } from "astro:assets"; +import topBanner from "~/assets/top-page/ja-comic-why-mapconductor.jpg"; + + + +MapConductor(マップコンダクター) ♪ 複数の地図プロバイダを単一のAPIで扱えるAndroid向け統一地図SDK -# MapConductorについて - - ## サポートされている機能 ## プロバイダーパッケージ diff --git a/docs/src/content/docs/ja/mapviewholder/arcgis-2d.mdx b/docs/src/content/docs/ja/mapviewholder/arcgis-2d.mdx new file mode 100644 index 0000000..12af3be --- /dev/null +++ b/docs/src/content/docs/ja/mapviewholder/arcgis-2d.mdx @@ -0,0 +1,60 @@ +--- +title: ArcGISMapView2D(2D フラットビュー) +head: + - tag: meta + attrs: + name: description + content: ArcGISMapView2D — MapConductor iOS SDK の ArcGIS 2D フラットマップビュー +--- +import ArcGIS2DBasicUsage from '~/components/mapviewholder/arcgis-2d/ArcGIS2DBasicUsage.astro'; +import ArcGIS2DInitializerParams from '~/components/mapviewholder/arcgis-2d/ArcGIS2DInitializerParams.astro'; +import ArcGIS2DMapHolderExample from '~/components/mapviewholder/arcgis-2d/ArcGIS2DMapHolderExample.astro'; + +`ArcGISMapView2D` は ArcGIS の 2D フラット地図を表示する SwiftUI ビューです。通常の `ArcGISMapView` が 3D `SceneView` ベースであるのに対し、`ArcGISMapView2D` は ArcGIS の 2D `MapView` を使用します。 + +## 概要 + +2D 表示に特化した ArcGIS マップが必要な場合に使用します。`ArcGISMapViewState` は通常の `ArcGISMapView` と共用できるため、別途状態型を用意する必要はありません。 + +`ArcGISMapView2D` は `MapConductorForArcGIS` モジュールで提供されます。 + +## ArcGISMapView との違い + +| | `ArcGISMapView` | `ArcGISMapView2D` | +|---|---|---| +| ネイティブビュー | 3D `SceneView` | 2D `MapView` | +| チルトサポート | あり | なし(常に `0`) | +| 3D 建物表示 | あり | なし | +| 状態型 | `ArcGISMapViewState` | `ArcGISMapViewState`(共用) | + +- 3D `SceneView` の代わりに 2D `MapView` を使用します +- `tilt` は常に `0` に固定され、3D 視点は利用できません +- 建物などの 3D 表示や標高レンダリングは行いません +- 3D 表示が不要な画面で軽量に扱えます +- `ArcGISMapViewState` は通常の `ArcGISMapView` と共用できます + +## 基本的な使い方 + + + +## イニシャライザーのパラメーター + + + +- **`state`**: `ArcGISMapViewState`。カメラ位置、地図デザイン、コントローラなどを保持します。 +- **`onMapLoaded`**: 地図の読み込みが完了したときに呼び出されます。 +- **`onMapClick`**: ユーザーがマップ上のオーバーレイではない部分をタップしたときに呼び出されます。 +- **`onMapLongClick`**: ユーザーがマップ上のオーバーレイではない部分を長押ししたときに呼び出されます。 +- **`onCameraMoveStart`**: カメラ移動が開始されたときに呼び出されます。 +- **`onCameraMove`**: カメラ移動中に呼び出されます。 +- **`onCameraMoveEnd`**: カメラ移動が終了したときに呼び出されます。 +- **`sdkInitialize`**: ArcGIS SDK の初期化(API キー設定)を行うクロージャ。プロセス内で一度だけ呼び出されます。 +- **`content`**: マーカー、ポリライン、ポリゴンなどの地図オーバーレイを配置する `@MapViewContentBuilder` クロージャ。 + +## 注意事項 + +`ArcGISMapView2D` では `tilt` は常に `0` に固定されます。`MapCameraPosition` に `tilt` を指定しても 2D `MapView` では無視され、3D 視点や建物の立体表示は行われません。 + +`ArcGISDesign` で表現できるベースマップ変更には、ネイティブ SDK に直接アクセスせず `mapViewState.mapDesignType` を更新してください。ネイティブの `ArcGIS.Map` にアクセスするには `holder.map` をキャストします: + + diff --git a/docs/src/content/docs/ja/mapviewholder/arcgis.mdx b/docs/src/content/docs/ja/mapviewholder/arcgis.mdx new file mode 100644 index 0000000..d07fa53 --- /dev/null +++ b/docs/src/content/docs/ja/mapviewholder/arcgis.mdx @@ -0,0 +1,20 @@ +--- +title: ArcGIS MapViewHolder +head: + - tag: meta + attrs: + name: description + content: MapConductor iOS SDKのArcGISMapViewHolder — ネイティブArcGISインスタンスへのアクセス方法 +--- +import ArcGISSceneHolderExample from '~/components/mapviewholder/arcgis/ArcGISSceneHolderExample.astro'; +import ArcGISMapHolderExample from '~/components/mapviewholder/arcgis/ArcGISMapHolderExample.astro'; + +`ArcGISMapViewState.getMapViewHolder()` は `AnyMapViewHolder?` を返します。`ArcGISMapView`(3D)では `holder.map` を `ArcGIS.Scene` に、`ArcGISMapView2D`(2D)では `holder.map` を `ArcGIS.Map` にキャストできます。 + + + +2D マップでネイティブの `Map` にアクセスする場合: + + + +`ArcGISDesign` で表現できるベースマップ変更は、ネイティブ SDK に直接アクセスせずに `mapViewState.mapDesignType` を更新してください。 diff --git a/docs/src/content/docs/ja/mapviewholder/googlemaps.mdx b/docs/src/content/docs/ja/mapviewholder/googlemaps.mdx index 850b4b2..fae42de 100644 --- a/docs/src/content/docs/ja/mapviewholder/googlemaps.mdx +++ b/docs/src/content/docs/ja/mapviewholder/googlemaps.mdx @@ -6,16 +6,10 @@ head: name: description content: MapConductor iOS SDKのGoogleMapViewHolder — ネイティブGoogle Mapsインスタンスへのアクセス方法 --- +import GoogleMapsHolderExample from '~/components/mapviewholder/googlemaps/GoogleMapsHolderExample.astro'; `GoogleMapViewState.getMapViewHolder()` は `AnyMapViewHolder?` を返します。ネイティブの Google Maps オブジェクトは `Any` として保存されており、`GMSMapView` にキャストできます。 -```swift -import GoogleMaps - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? GMSMapView { - mapView.mapType = .hybrid -} -``` + ネイティブ SDK の型にキャストせずに座標変換を行うには、`toScreenOffset(position:)` と `fromScreenOffset(offset:)` を使用してください。 diff --git a/docs/src/content/docs/ja/mapviewholder/here-maps.mdx b/docs/src/content/docs/ja/mapviewholder/here-maps.mdx new file mode 100644 index 0000000..b4ae175 --- /dev/null +++ b/docs/src/content/docs/ja/mapviewholder/here-maps.mdx @@ -0,0 +1,15 @@ +--- +title: HERE Maps MapViewHolder +head: + - tag: meta + attrs: + name: description + content: MapConductor iOS SDKのHereMapViewHolder — ネイティブHEREインスタンスへのアクセス方法 +--- +import HereMapsHolderExample from '~/components/mapviewholder/here-maps/HereMapsHolderExample.astro'; + +`HereMapViewState.getMapViewHolder()` は `AnyMapViewHolder?` を返します。ネイティブビューは HERE SDK の `MapView` にキャストでき、`holder.map` は `MapScene` にキャストできます。 + + + +`HereMapDesign` で表現できるスタイル変更は、ネイティブ SDK に直接アクセスせずに `mapViewState.mapDesignType` を更新してください。 diff --git a/docs/src/content/docs/ja/mapviewholder/index.mdx b/docs/src/content/docs/ja/mapviewholder/index.mdx index 6ba1e21..2f233e9 100644 --- a/docs/src/content/docs/ja/mapviewholder/index.mdx +++ b/docs/src/content/docs/ja/mapviewholder/index.mdx @@ -6,18 +6,15 @@ head: name: description content: MapConductor iOS SDKのMapViewHolder — ネイティブマップインスタンスへのアクセス方法 --- +import ScreenOffsetExample from '~/components/mapviewholder/index/ScreenOffsetExample.astro'; +import CastMapViewExample from '~/components/mapviewholder/index/CastMapViewExample.astro'; +import GuardHolderExample from '~/components/mapviewholder/index/GuardHolderExample.astro'; `MapViewStateProtocol.getMapViewHolder()` は `AnyMapViewHolder?` を返します。 MapConductor の共通 API に含まれないネイティブ SDK の機能へのエスケープハッチとしてホルダーを使用してください。通常のマーカー、シェイプ、カメラ、イベント処理には必要ありません。 -```swift -if let holder = mapViewState.getMapViewHolder() { - let screenPoint = holder.toScreenOffset( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671) - ) -} -``` + `AnyMapViewHolder` は以下を公開しています: @@ -29,23 +26,12 @@ if let holder = mapViewState.getMapViewHolder() { `GoogleMapViewHolder` などのプロバイダー固有のホルダークラスは、現在のソースでは内部実装の詳細となっています。ネイティブ SDK へのアクセスが必要な場合は、`holder.mapView` または `holder.map` をネイティブ SDK の型にキャストしてください。 -```swift -import GoogleMaps - -if let holder = mapViewState.getMapViewHolder(), - let googleMap = holder.mapView as? GMSMapView { - googleMap.mapType = .hybrid -} -``` + ## 準備完了の確認 ネイティブマップが準備できる前はホルダーが `nil` になることがあります。マップが作成済みの UI の時点でアクセスし、常にオプショナル値をガードしてください: -```swift -guard let holder = mapViewState.getMapViewHolder() else { - return -} -``` + ポータブルな機能には MapConductor の API を優先してください。プロバイダー固有の動作については、マップコードの共有部分を維持するために、キャストをプロバイダー固有の画面の近くに留めてください。 diff --git a/docs/src/content/docs/ja/mapviewholder/mapbox.mdx b/docs/src/content/docs/ja/mapviewholder/mapbox.mdx index 474d34d..7dd492f 100644 --- a/docs/src/content/docs/ja/mapviewholder/mapbox.mdx +++ b/docs/src/content/docs/ja/mapviewholder/mapbox.mdx @@ -6,19 +6,8 @@ head: name: description content: MapConductor iOS SDKのMapboxMapViewHolder — ネイティブMapboxインスタンスへのアクセス方法 --- +import MapboxHolderExample from '~/components/mapviewholder/mapbox/MapboxHolderExample.astro'; `MapboxViewState.getMapViewHolder()` は `AnyMapViewHolder?` を返します。ネイティブビューは Mapbox の `MapView` にキャストでき、`holder.map` は `MapboxMap` にキャストできます。 -```swift -import MapboxMaps - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? MapView { - mapView.mapboxMap.setCamera( - to: CameraOptions( - center: CLLocationCoordinate2D(latitude: 35.6812, longitude: 139.7671), - zoom: 14 - ) - ) -} -``` + diff --git a/docs/src/content/docs/ja/mapviewholder/mapkit.mdx b/docs/src/content/docs/ja/mapviewholder/mapkit.mdx index b97d990..c1a0239 100644 --- a/docs/src/content/docs/ja/mapviewholder/mapkit.mdx +++ b/docs/src/content/docs/ja/mapviewholder/mapkit.mdx @@ -6,20 +6,13 @@ head: name: description content: MapConductor iOS SDKのMapKitMapViewHolder — ネイティブMapKitインスタンスへのアクセス方法 --- +import MapKitHolderExample from '~/components/mapviewholder/mapkit/MapKitHolderExample.astro'; +import MapKitDesignTypeExample from '~/components/mapviewholder/mapkit/MapKitDesignTypeExample.astro'; `MapKitViewState.getMapViewHolder()` は `AnyMapViewHolder?` を返します。ネイティブの MapKit オブジェクトは `MKMapView` にキャストできます。 -```swift -import MapKit - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? MKMapView { - mapView.showsTraffic = true -} -``` + SDK でカバーされているマップタイプの変更には、キャストの代わりに `mapDesignType` を使用してください: -```swift -mapViewState.mapDesignType = MapKitMapDesign.Hybrid -``` + diff --git a/docs/src/content/docs/ja/mapviewholder/maplibre.mdx b/docs/src/content/docs/ja/mapviewholder/maplibre.mdx index 8507f9c..c9f8fb5 100644 --- a/docs/src/content/docs/ja/mapviewholder/maplibre.mdx +++ b/docs/src/content/docs/ja/mapviewholder/maplibre.mdx @@ -6,18 +6,8 @@ head: name: description content: MapConductor iOS SDKのMapLibreMapViewHolder — ネイティブMapLibreインスタンスへのアクセス方法 --- +import MapLibreHolderExample from '~/components/mapviewholder/maplibre/MapLibreHolderExample.astro'; `MapLibreViewState.getMapViewHolder()` は `AnyMapViewHolder?` を返します。ネイティブの MapLibre オブジェクトは `MLNMapView` にキャストできます。 -```swift -import MapLibre - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? MLNMapView { - mapView.setCenter( - CLLocationCoordinate2D(latitude: 35.6812, longitude: 139.7671), - zoomLevel: 14, - animated: true - ) -} -``` + diff --git a/docs/src/content/docs/ja/modules.mdx b/docs/src/content/docs/ja/modules.mdx index d952f68..f7a0f6a 100644 --- a/docs/src/content/docs/ja/modules.mdx +++ b/docs/src/content/docs/ja/modules.mdx @@ -7,6 +7,9 @@ head: content: MapConductor iOS SDKのモジュール一覧 — 各地図プロバイダ向けSwift Package Managerプロダクト --- +import ModulesPackageSwiftExample from '~/components/modules/ModulesPackageSwiftExample.astro'; +import ModulesOptionalPackagesExample from '~/components/modules/ModulesOptionalPackagesExample.astro'; + リポジトリは独立した Swift パッケージに分割されており、各アプリケーションは実際に使用するプロバイダーとユーティリティのみに依存できます。典型的なアプリは `MapConductorCore` と 1 つのプロバイダーモジュールに依存します。 | パッケージリポジトリ | プロダクト | 主な型 | @@ -16,8 +19,11 @@ head: | `ios-for-mapbox` | `MapConductorForMapbox` | `MapboxViewState`, `MapboxMapView`, `MapboxMapDesign` | | `ios-for-mapkit` | `MapConductorForMapKit` | `MapKitViewState`, `MapKitMapView`, `MapKitMapDesign` | | `ios-for-maplibre` | `MapConductorForMapLibre` | `MapLibreViewState`, `MapLibreMapView`, `MapLibreDesign` | +| `ios-for-arcgis` | `MapConductorForArcGIS` | `ArcGISMapViewState`, `ArcGISMapView`, `ArcGISMapDesign` | +| `ios-for-here` | `MapConductorForHERE` | `HereMapViewState`, `HereMapView`, `HereMapDesign` | | `ios-heatmap` | `MapConductorHeatmap` | `HeatmapOverlay`, `HeatmapOverlayState`, `HeatmapPointView`, `HeatmapPoints` | | `ios-marker-cluster` | `MapConductorMarkerCluster` | `MarkerClusterGroup`, `MarkerClusterGroupState`, `MarkerClusterStrategy` | +| `ios-geojson-layer` | `MapConductorGeoJSON` | `GeoJSONLayerState`, `GeoJSONLayer`, `GeoJSONFeature` | ## コアパッケージ @@ -45,43 +51,12 @@ head: - `MapConductorHeatmap` は `HeatmapOverlay`、`HeatmapPointView`、`HeatmapPoints`、`HeatmapOverlayState` を使ったタイルベースのヒートマップレンダリングを追加します。 - `MapConductorMarkerCluster` は `MarkerClusterGroup`、`MarkerClusterGroupState`、`MarkerClusterStrategy` を使ったマーカークラスタリングを追加します。 +- `MapConductorGeoJSON` は `GeoJSONLayer`、`GeoJSONLayerState`、`GeoJSONFeature` を使ったタイルベースの GeoJSON レイヤーレンダリングを追加します。 ## Package.swift のサンプル -```swift -dependencies: [ - .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}"), -], -targets: [ - .target( - name: "App", - dependencies: [ - .product(name: "MapConductorCore", package: "ios-sdk-core"), - .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), - ] - ) -] -``` + ## オプションモジュールを使ったサンプル -```swift -dependencies: [ - .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-heatmap", from: "{HEATMAP_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "{MARKER_CLUSTERING_MODULE_VERSION}"), -], -targets: [ - .target( - name: "App", - dependencies: [ - .product(name: "MapConductorCore", package: "ios-sdk-core"), - .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), - .product(name: "MapConductorHeatmap", package: "ios-heatmap"), - .product(name: "MapConductorMarkerCluster", package: "ios-marker-cluster"), - ] - ) -] -``` + diff --git a/docs/src/content/docs/ja/setup/arcgis.mdx b/docs/src/content/docs/ja/setup/arcgis.mdx new file mode 100644 index 0000000..4ff8266 --- /dev/null +++ b/docs/src/content/docs/ja/setup/arcgis.mdx @@ -0,0 +1,123 @@ +--- +title: ArcGIS セットアップ +head: + - tag: meta + attrs: + name: description + content: MapConductor iOS SDKでArcGISを使うためのステップバイステップ導入ガイド +--- + +import ArcGISInitExample from '~/components/setup/arcgis/ArcGISInitExample.astro'; +import ArcGISInitSdkInitializeExample from '~/components/setup/arcgis/ArcGISInitSdkInitializeExample.astro'; +import ArcGISVerification3DExample from '~/components/setup/arcgis/ArcGISVerification3DExample.astro'; +import ArcGISVerification2DExample from '~/components/setup/arcgis/ArcGISVerification2DExample.astro'; +import ArcGISStylePresetsExample from '~/components/setup/arcgis/ArcGISStylePresetsExample.astro'; +import ArcGISCustomDesignExample from '~/components/setup/arcgis/ArcGISCustomDesignExample.astro'; + +このセクションでは、MapConductor と ArcGIS Maps SDK for Swift を統合するセットアップ手順について説明します。 + +アプリがすでに ArcGIS Maps SDK for Swift に依存している場合、ArcGIS のベースマップを `ArcGISDesign` で選択したい場合、または 3D/2D の ArcGIS マップビューを使いたい場合にこのプロバイダーを使用してください。 + +## 前提条件 + +- iOS 開発環境(Xcode 15 以上) +- ArcGIS 開発者アカウント +- ArcGIS API キー +- iOS デプロイターゲットが 17.0 以上に設定されていること + +## セットアップ手順 + +### 1. ArcGIS の設定 + +1. [ArcGIS Location Platform](https://location.arcgis.com/) または [ArcGIS Developers](https://developers.arcgis.com/) にアクセスします +2. プロジェクトまたはアプリ用の API キーを作成します +3. 必要な ArcGIS サービス(ベースマップ、ジオコーディング、ルーティングなど)を有効にします +4. API キーをアプリの用途に合わせて制限します + +### 2. Swift Package Manager の設定 + +Xcode プロジェクトに MapConductorForArcGIS を追加します: + +1. Xcode で **File > Add Package Dependencies** に移動します +2. パッケージ URL を入力します: + - `https://github.com/MapConductor/ios-for-arcgis` + +ArcGIS Maps SDK for Swift は依存関係として自動的にインストールされます。追加のパッケージは必要ありません。 + +### 3. アプリで ArcGIS を初期化する + +マップビューが表示される前に、一度だけ `arcGISApiKeyInitialize(apiKey:)` を呼び出します。推奨される場所は `@main` App 構造体です: + + + +または、`ArcGISMapView` または `ArcGISMapView2D` の `sdkInitialize` パラメーターで呼び出すこともできます: + + + +## 動作確認 + +以下のコードでアプリをビルドして実行し、3D マップのセットアップを確認します: + + + +2D マップを表示する場合は、同じ `ArcGISMapViewState` を `ArcGISMapView2D` に渡します: + + + +エラーなくマップが表示されれば、セットアップは正常に完了しています。基本的な操作(ズーム、パン、3D 表示ではチルト)をテストして、すべてが正常に機能していることを確認してください。 + +## トラブルシューティング + +### よくある問題 + +**マップが表示されない(空白画面)** + +- `arcGISApiKeyInitialize(apiKey:)` に渡す API キーが正しいことを確認してください +- マップビューが作成される前に `arcGISApiKeyInitialize(apiKey:)` が呼び出されていることを確認してください +- API キーで必要な ArcGIS サービスが有効になっていることを確認してください +- iOS デプロイターゲットが 17.0 以上に設定されていることを確認してください + +**API キーエラー** + +- API キーの値を再確認してください +- キーが取り消されていないことを確認してください +- キーの制限がアプリの実際のバンドル ID や利用サービスと一致していることを確認してください + +**ビルドエラー** + +- `MapConductorForArcGIS` がターゲットに正しくリンクされていることを確認してください +- Swift Package Manager の依存関係を更新してください +- ビルドフォルダーをクリアします(Cmd+Shift+K) +- 派生データを削除します: `~/Library/Developer/Xcode/DerivedData` + +### 位置情報サービス + +位置情報ベースの機能を有効にするには、`Info.plist` に以下のキーを追加します: + +```xml +NSLocationWhenInUseUsageDescription +We need your location to display on the map +``` + +## マップスタイル + +`ArcGISDesign` の静的プリセットを使ってマップスタイルを設定します: + + + +既知の ID から作成する場合は `ArcGISDesign.Create(id:)` を使用します: + + + +## 次のステップ + +ArcGIS Maps SDK for Swift が正しく設定されたら、[マップビューコンポーネントのドキュメント](/components/mapviewcomponent/) に記載されているように `ArcGISMapView` または `ArcGISMapView2D` を使用できます。 + +その他のサンプルについては、[はじめる](/get-started/) チュートリアルを参照してください。 diff --git a/docs/src/content/docs/ja/setup/google-maps.mdx b/docs/src/content/docs/ja/setup/google-maps.mdx index cc27c62..e42c671 100644 --- a/docs/src/content/docs/ja/setup/google-maps.mdx +++ b/docs/src/content/docs/ja/setup/google-maps.mdx @@ -7,6 +7,11 @@ head: content: MapConductor iOS SDKでGoogle Mapsを使うためのステップバイステップ導入ガイド --- +import GoogleMapsInitExample from '~/components/setup/google-maps/GoogleMapsInitExample.astro'; +import GoogleMapsInitSdkInitializeExample from '~/components/setup/google-maps/GoogleMapsInitSdkInitializeExample.astro'; +import GoogleMapsVerificationExample from '~/components/setup/google-maps/GoogleMapsVerificationExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + このセクションでは、MapConductor と Google Maps SDK を統合するセットアップ手順について説明します。 アプリがすでに Google Maps SDK for iOS に依存している場合、または `GoogleMapDesign` を通じて Google マップタイプが必要な場合にこのプロバイダーを使用してください。 @@ -40,33 +45,11 @@ Xcode プロジェクトに Google Maps SDK と MapConductor を追加します マップビューが表示される前に、一度だけ `GMSServices.provideAPIKey(_:)` を呼び出します。推奨される場所は `@main` App 構造体です: -```swift -import SwiftUI -import GoogleMaps -import MapConductorForGoogleMaps - -@main -struct MyApp: App { - init() { - GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` + または、`GoogleMapView` の `sdkInitialize` パラメーターで呼び出すこともできます: -```swift -GoogleMapView( - state: mapState, - sdkInitialize: { GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY") } -) { ... } -``` + ### 4. 位置情報の権限を追加する(必要な場合) @@ -81,25 +64,7 @@ GoogleMapView( 以下のコードでアプリをビルドして実行し、セットアップを確認します: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject var mapState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 37.7749, longitude: -122.4194), - zoom: 12.0 - ) - ) - - var body: some View { - GoogleMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + エラーなくマップが表示されれば、セットアップは正常に完了しています。基本的な操作(ズーム、パン)をテストして、すべてが正常に機能していることを確認してください。 @@ -135,19 +100,7 @@ struct ContentView: View { 1. Info.plist に位置情報の権限を追加します(手順 4 を参照) 2. アプリ内でユーザーへの許可リクエストを行います: -```swift -import CoreLocation - -class LocationManager: NSObject, CLLocationManagerDelegate { - let manager = CLLocationManager() - - override init() { - super.init() - manager.delegate = self - manager.requestWhenInUseAuthorization() - } -} -``` + ## 次のステップ diff --git a/docs/src/content/docs/ja/setup/here-maps.mdx b/docs/src/content/docs/ja/setup/here-maps.mdx new file mode 100644 index 0000000..5fdafc3 --- /dev/null +++ b/docs/src/content/docs/ja/setup/here-maps.mdx @@ -0,0 +1,112 @@ +--- +title: HERE Maps セットアップ +head: + - tag: meta + attrs: + name: description + content: MapConductor iOS SDKでHERE Mapsを使うためのステップバイステップ導入ガイド +--- + +import HereMapsInitExample from '~/components/setup/here-maps/HereMapsInitExample.astro'; +import HereMapsInitSdkInitializeExample from '~/components/setup/here-maps/HereMapsInitSdkInitializeExample.astro'; +import HereMapsVerificationExample from '~/components/setup/here-maps/HereMapsVerificationExample.astro'; +import HereMapsStylePresetsExample from '~/components/setup/here-maps/HereMapsStylePresetsExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + +このセクションでは、MapConductor と HERE SDK を統合するセットアップ手順について説明します。 + +アプリがすでに HERE SDK に依存している場合、または `HereMapDesign` を通じて HERE のマップスキームが必要な場合にこのプロバイダーを使用してください。 + +## 前提条件 + +- iOS 開発環境(Xcode 15 以上) +- HERE Platform アカウント +- HERE SDK のアクセスキー ID とアクセスキーシークレット +- iOS デプロイターゲットが 16.0 以上に設定されていること + +## セットアップ手順 + +### 1. HERE Platform の設定 + +1. [HERE Platform](https://platform.here.com/) にアクセスします +2. プロジェクトを作成するか、既存のプロジェクトを選択します +3. HERE SDK 用のアクセスキーを作成します +4. アクセスキー ID とアクセスキーシークレットをコピーします + +### 2. Swift Package Manager の設定 + +Xcode プロジェクトに MapConductorForHERE を追加します: + +1. Xcode で **File > Add Package Dependencies** に移動します +2. パッケージ URL を入力します: + - `https://github.com/MapConductor/ios-for-here` + +### 3. アプリで HERE SDK を初期化する + +マップビューが表示される前に、一度だけ `initializeHERE(accessKeyId:accessKeySecret:)` を呼び出します。推奨される場所は `@main` App 構造体です: + + + +または、`HereMapView` の `sdkInitialize` パラメーターで呼び出すこともできます: + + + +### 4. 位置情報の権限を追加する(必要な場合) + +アプリが HERE SDK で位置情報サービスを使用する場合、`Info.plist` に追加します: + +```xml +NSLocationWhenInUseUsageDescription +We need your location to display on the map +``` + +## 動作確認 + +以下のコードでアプリをビルドして実行し、セットアップを確認します: + + + +エラーなくマップが表示されれば、セットアップは正常に完了しています。基本的な操作(ズーム、パン)をテストして、すべてが正常に機能していることを確認してください。 + +## トラブルシューティング + +### よくある問題 + +**マップが表示されない(空白画面)** + +- `initializeHERE(accessKeyId:accessKeySecret:)` に渡すアクセスキー ID とアクセスキーシークレットが正しいことを確認してください +- マップビューが作成される前に `initializeHERE(accessKeyId:accessKeySecret:)` が呼び出されていることを確認してください +- iOS デプロイターゲットが 16.0 以上に設定されていることを確認してください +- `MapConductorForHERE` がターゲットに正しくリンクされていることを確認してください + +**HERE SDK の初期化エラー** + +- アクセスキー ID とアクセスキーシークレットの値を再確認してください +- キーが無効化または削除されていないことを確認してください +- 初期化時の `throw` を捕捉し、エラー内容をログに出力してください + +### 位置情報サービス + +位置情報ベースの機能を有効にするには、`Info.plist` に位置情報の権限を追加し(手順 4 を参照)、アプリ内で許可リクエストを行います: + + + +## マップスタイル + +`HereMapDesign` の静的プリセットを使ってマップスタイルを設定します: + + + +## 次のステップ + +HERE SDK が正しく設定されたら、[マップビューコンポーネントのドキュメント](/components/mapviewcomponent/) に記載されているように `HereMapView` を使用できます。 + +その他のサンプルについては、[はじめる](/get-started/) チュートリアルを参照してください。 diff --git a/docs/src/content/docs/ja/setup/index.mdx b/docs/src/content/docs/ja/setup/index.mdx index 60774b5..0a9b0b4 100644 --- a/docs/src/content/docs/ja/setup/index.mdx +++ b/docs/src/content/docs/ja/setup/index.mdx @@ -7,12 +7,12 @@ head: content: MapConductor iOS SDKのセットアップ概要 — 各地図プロバイダのSwift Package Manager導入手順 --- +import PackageExample from '~/components/setup/overview/PackageExample.astro'; +import MinimalGoogleMapsExample from '~/components/setup/overview/MinimalGoogleMapsExample.astro'; + `MapConductorCore` と 1 つ以上のプロバイダーパッケージを使用します。 -```swift -.package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), -.package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}") -``` + プロバイダー SDK のセットアップは引き続き必要です: @@ -21,31 +21,10 @@ head: - MapKit: グローバルな SDK 初期化は不要 - MapLibre: `MapLibreDesign` を選択する - ArcGIS: 認証が必要な場合は `MapConductorForArcGIS` の認証ヘルパーを使用する +- HERE Maps: HERE SDK の初期化時にアクセスキー ID とシークレットを指定する ## Google Maps の最小サンプル -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) - ) - - var body: some View { - GoogleMapView(state: mapViewState) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(label: "T") - ) - } - } -} -``` + パッケージと認証情報の詳細については、各プロバイダーのセットアップページを参照してください。 diff --git a/docs/src/content/docs/ja/setup/mapbox.mdx b/docs/src/content/docs/ja/setup/mapbox.mdx index 822a61e..b7bacbe 100644 --- a/docs/src/content/docs/ja/setup/mapbox.mdx +++ b/docs/src/content/docs/ja/setup/mapbox.mdx @@ -7,6 +7,12 @@ head: content: MapConductor iOS SDKでMapboxを使うためのステップバイステップ導入ガイド --- +import MapboxInitExample from '~/components/setup/mapbox/MapboxInitExample.astro'; +import MapboxInitSdkInitializeExample from '~/components/setup/mapbox/MapboxInitSdkInitializeExample.astro'; +import MapboxVerificationExample from '~/components/setup/mapbox/MapboxVerificationExample.astro'; +import MapboxStylePresetsExample from '~/components/setup/mapbox/MapboxStylePresetsExample.astro'; +import MapboxCustomStyleExample from '~/components/setup/mapbox/MapboxCustomStyleExample.astro'; + このセクションでは、MapConductor と Mapbox SDK を統合するセットアップ手順について説明します。 アプリがすでに Mapbox Maps SDK に依存している場合、または `MapboxMapDesign` を通じて Mapbox スタイル URL が必要な場合にこのプロバイダーを使用してください。 @@ -39,57 +45,17 @@ Mapbox Maps SDK は依存関係として自動的にインストールされま マップビューが表示される前に、一度だけ `initializeMapbox(accessToken:)` を呼び出します。推奨される場所は `@main` App 構造体です: -```swift -import SwiftUI -import MapConductorForMapbox - -@main -struct MyApp: App { - init() { - initializeMapbox(accessToken: "YOUR_MAPBOX_PUBLIC_TOKEN") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` + または、`MapboxMapView` の `sdkInitialize` パラメーターで呼び出すこともできます: -```swift -MapboxMapView( - state: mapState, - sdkInitialize: { initializeMapbox(accessToken: "YOUR_MAPBOX_PUBLIC_TOKEN") } -) { ... } -``` + ## 動作確認 以下のコードでアプリをビルドして実行し、セットアップを確認します: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForMapbox - -struct ContentView: View { - @StateObject var mapState = MapboxViewState( - mapDesignType: MapboxMapDesign.Standard, - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 37.7749, longitude: -122.4194), - zoom: 12.0 - ) - ) - - var body: some View { - MapboxMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + エラーなくマップが表示されれば、セットアップは正常に完了しています。基本的な操作(ズーム、パン)をテストして、すべてが正常に機能していることを確認してください。 @@ -124,25 +90,16 @@ struct ContentView: View { `MapboxMapDesign` の静的プリセットを使ってマップスタイルを設定します: -```swift -// スタンダードスタイル(デフォルト) -mapState.mapDesignType = MapboxMapDesign.Standard - -// ストリートスタイル -mapState.mapDesignType = MapboxMapDesign.Streets - -// 道路とラベル付きの衛星画像 -mapState.mapDesignType = MapboxMapDesign.SatelliteStreets - -// ダークスタイル -mapState.mapDesignType = MapboxMapDesign.Dark -``` + カスタムスタイルには `MapboxMapDesign.custom(styleURI:)` を使用します: -```swift -mapState.mapDesignType = MapboxMapDesign.custom(styleURI: "mapbox://styles/user/abc123") -``` + ## 次のステップ diff --git a/docs/src/content/docs/ja/setup/mapkit.mdx b/docs/src/content/docs/ja/setup/mapkit.mdx index dd0eaee..4c0aae9 100644 --- a/docs/src/content/docs/ja/setup/mapkit.mdx +++ b/docs/src/content/docs/ja/setup/mapkit.mdx @@ -7,6 +7,10 @@ head: content: MapConductor iOS SDKでApple MapKitを使うためのステップバイステップ導入ガイド --- +import MapKitVerificationExample from '~/components/setup/mapkit/MapKitVerificationExample.astro'; +import MapKitStylePresetsExample from '~/components/setup/mapkit/MapKitStylePresetsExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + このセクションでは、MapConductor と MapKit を統合するセットアップ手順について説明します。 外部依存関係や API キーなしに Apple のビルトインマップ SDK を使用したい場合や、Apple プラットフォームのみをターゲットにしている場合にこのプロバイダーを使用してください。 @@ -41,26 +45,7 @@ Xcode プロジェクトに MapConductorForMapKit を追加します: 以下のコードでアプリをビルドして実行し、セットアップを確認します: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForMapKit - -struct ContentView: View { - @StateObject var mapState = MapKitViewState( - mapDesignType: MapKitMapDesign.Standard, - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 12.0 - ) - ) - - var body: some View { - MapKitMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + エラーなくマップが表示されれば、セットアップは正常に完了しています。基本的な操作(ズーム、パン)をテストして、すべてが正常に機能していることを確認してください。 @@ -83,37 +68,18 @@ struct ContentView: View { 位置情報ベースの機能を有効にするには、`Info.plist` に位置情報の権限を追加し(手順 2 を参照)、アプリ内で許可リクエストを行います: -```swift -import CoreLocation - -class LocationManager: NSObject, CLLocationManagerDelegate { - let manager = CLLocationManager() - - override init() { - super.init() - manager.delegate = self - manager.requestWhenInUseAuthorization() - } -} -``` + ## マップスタイル `MapKitMapDesign` の静的プリセットを使ってマップスタイルを設定します: -```swift -// 標準ストリートマップ -mapState.mapDesignType = MapKitMapDesign.Standard - -// 衛星画像 -mapState.mapDesignType = MapKitMapDesign.Satellite - -// 道路とラベル付きの衛星画像 -mapState.mapDesignType = MapKitMapDesign.Hybrid - -// ミュートされた標準スタイル -mapState.mapDesignType = MapKitMapDesign.MutedStandard -``` + ## 次のステップ diff --git a/docs/src/content/docs/ja/setup/maplibre.mdx b/docs/src/content/docs/ja/setup/maplibre.mdx index 625c15d..62bcd24 100644 --- a/docs/src/content/docs/ja/setup/maplibre.mdx +++ b/docs/src/content/docs/ja/setup/maplibre.mdx @@ -7,6 +7,11 @@ head: content: MapConductor iOS SDKでMapLibreを使うためのステップバイステップ導入ガイド --- +import MapLibreVerificationExample from '~/components/setup/maplibre/MapLibreVerificationExample.astro'; +import MapLibreStylePresetsExample from '~/components/setup/maplibre/MapLibreStylePresetsExample.astro'; +import MapLibreCustomStyleExample from '~/components/setup/maplibre/MapLibreCustomStyleExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + このセクションでは、MapConductor と MapLibre SDK を統合するセットアップ手順について説明します。 プロプライエタリな API キーなしにオープンソースのマップレンダラーが必要な場合や、`MapLibreDesign` を使って独自のマップタイルをホストしたい場合にこのプロバイダーを使用してください。 @@ -44,26 +49,7 @@ MapLibre Native SDK は依存関係として自動的にインストールされ 以下のコードでアプリをビルドして実行し、セットアップを確認します: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForMapLibre - -struct ContentView: View { - @StateObject var mapState = MapLibreViewState( - mapDesignType: MapLibreDesign.OsmBright, - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 12.0 - ) - ) - - var body: some View { - MapLibreMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + エラーなくマップが表示されれば、セットアップは正常に完了しています。基本的な操作(ズーム、パン)をテストして、すべてが正常に機能していることを確認してください。 @@ -87,47 +73,22 @@ struct ContentView: View { 位置情報ベースの機能を有効にするには、`Info.plist` に位置情報の権限を追加し(手順 2 を参照)、アプリ内で許可リクエストを行います: -```swift -import CoreLocation - -class LocationManager: NSObject, CLLocationManagerDelegate { - let manager = CLLocationManager() - - override init() { - super.init() - manager.delegate = self - manager.requestWhenInUseAuthorization() - } -} -``` + ## マップスタイル `MapLibreDesign` の静的プリセットを使ってマップスタイルを設定します: -```swift -// OpenStreetMap Bright(英語) -mapState.mapDesignType = MapLibreDesign.OsmBright - -// OpenStreetMap Bright(日本語) -mapState.mapDesignType = MapLibreDesign.OsmBrightJa - -// MapTiler Toner(英語) -mapState.mapDesignType = MapLibreDesign.MapTilerTonerEn - -// 軽量デモタイル -mapState.mapDesignType = MapLibreDesign.DemoTiles -``` + カスタムスタイルには `MapLibreDesign` を直接構築します: -```swift -let custom = MapLibreDesign( - id: "custom", - styleJsonURL: "https://example.com/style.json" -) -mapState.mapDesignType = custom -``` + ## 次のステップ diff --git a/docs/src/content/docs/ja/states/circle-state.mdx b/docs/src/content/docs/ja/states/circle-state.mdx index bd445ee..80d3608 100644 --- a/docs/src/content/docs/ja/states/circle-state.mdx +++ b/docs/src/content/docs/ja/states/circle-state.mdx @@ -7,6 +7,9 @@ head: content: CircleState — MapConductor iOS SDKで円の半径・塗りつぶし・枠線を動的に更新する方法 --- +import CircleStateBasicExample from '~/components/states/circle-state/CircleStateBasicExample.astro'; +import CircleStateSelectionExample from '~/components/states/circle-state/CircleStateSelectionExample.astro'; + `CircleState` は `Circle(state:)` で使用される `ObservableObject` です。 マップコンテンツの初回レンダリング後にサークルの中心、半径、スタイル、Z インデックス、またはクリック動作を変更する必要がある場合に使用してください。 @@ -27,27 +30,10 @@ head: サークルのサイズには `radiusMeters` を使用してください。透明度を表現するには `fillColor` または `strokeColor` のアルファ値を使用してください。 -```swift -let circleState = CircleState( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500, - fillColor: UIColor.blue.withAlphaComponent(0.2) -) - -Circle(state: circleState) - -circleState.radiusMeters = 1_000 -circleState.center = GeoPoint(latitude: 35.6895, longitude: 139.6917) -``` + ## 選択の例 -```swift -circleState.onClick = { event in - selectedCircleId = event.state.id -} - -circleState.strokeColor = selectedCircleId == circleState.id ? .red : .blue -``` + サークルがジオフェンスや検索エリアなどのモデルオブジェクトを表す場合は、`id` を安定した値に保ってください。 diff --git a/docs/src/content/docs/ja/states/groundimage-state.mdx b/docs/src/content/docs/ja/states/groundimage-state.mdx index 4ddeff4..e224546 100644 --- a/docs/src/content/docs/ja/states/groundimage-state.mdx +++ b/docs/src/content/docs/ja/states/groundimage-state.mdx @@ -7,6 +7,9 @@ head: content: GroundImageState — MapConductor iOS SDKで画像の境界・不透明度・ソースを動的に更新する方法 --- +import GroundImageStateBasicExample from '~/components/states/groundimage-state/GroundImageStateBasicExample.astro'; +import GroundImageStateRepositionExample from '~/components/states/groundimage-state/GroundImageStateRepositionExample.astro'; + `GroundImageState` は `GroundImage(state:)` で使用される `ObservableObject` です。 実行時に画像、不透明度、地理的バウンズ、またはクリックハンドラーが変化する場合に使用してください。 @@ -21,32 +24,12 @@ head: - `extra: Any?` - `onClick: OnGroundImageEventHandler?` -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) - -let groundImageState = GroundImageState( - bounds: bounds, - image: UIImage(named: "overlay")!, - opacity: 0.5 -) - -GroundImage(state: groundImageState) - -groundImageState.opacity = 1.0 -``` + ## 位置の変更 画像を移動またはリサイズするには `bounds` を更新してください: -```swift -groundImageState.bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.66, longitude: 139.73), - northEast: GeoPoint(latitude: 35.70, longitude: 139.77) -) -``` + 表示するラスター画像が変わった場合は `image` を更新してください。オーバーレイレンダラーで使用するタイルサイズを制御するには `tileSize` を使用してください。 diff --git a/docs/src/content/docs/ja/states/marker-state.mdx b/docs/src/content/docs/ja/states/marker-state.mdx index a5495aa..0e1b787 100644 --- a/docs/src/content/docs/ja/states/marker-state.mdx +++ b/docs/src/content/docs/ja/states/marker-state.mdx @@ -7,6 +7,9 @@ head: content: MarkerState — MapConductor iOS SDKでマーカーの位置・アイコン・表示状態を動的に更新する方法 --- +import MarkerStateBasicExample from '~/components/states/marker-state/MarkerStateBasicExample.astro'; +import MarkerStateHandlersExample from '~/components/states/marker-state/MarkerStateHandlersExample.astro'; + `MarkerState` は `Marker(state:)` で使用される `ObservableObject` です。 マーカーの作成後にデータを変更する必要がある場合は `MarkerState` を使用してください。静的なマーカーであれば、通常は `Marker(position:...)` の直接イニシャライザーで十分です。 @@ -28,22 +31,7 @@ head: アプリ固有のデータには `extra` を、マーカーのテキストや画像の表示には `icon` を使用してください。 -```swift -let markerState = MarkerState( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - extra: "Tokyo Station", - icon: DefaultMarkerIcon(fillColor: .red, label: "T"), - draggable: true, - onClick: { state in - print("Clicked \(state.id), extra: \(String(describing: state.extra))") - } -) - -Marker(state: markerState) - -markerState.position = GeoPoint(latitude: 35.6895, longitude: 139.6917) -markerState.animate(.Bounce) -``` + 特定の値を変更した新しいマーカーステートを作成するには `copy(...)` を使用してください。 @@ -51,15 +39,7 @@ markerState.animate(.Bounce) イベントハンドラーはステートに保存されるため、他のマーカープロパティと一緒に差し替えることができます: -```swift -markerState.onClick = { state in - selectedMarkerId = state.id -} - -markerState.onDragEnd = { state in - saveCoordinate(state.position) -} -``` + ## 識別子 diff --git a/docs/src/content/docs/ja/states/polygon-state.mdx b/docs/src/content/docs/ja/states/polygon-state.mdx index dbafc3e..2891a9e 100644 --- a/docs/src/content/docs/ja/states/polygon-state.mdx +++ b/docs/src/content/docs/ja/states/polygon-state.mdx @@ -7,6 +7,9 @@ head: content: PolygonState — MapConductor iOS SDKでポリゴンの頂点・塗りつぶし・枠線を動的に更新する方法 --- +import PolygonStateBasicExample from '~/components/states/polygon-state/PolygonStateBasicExample.astro'; +import PolygonStateHolesExample from '~/components/states/polygon-state/PolygonStateHolesExample.astro'; + `PolygonState` は `Polygon(state:)` で使用される `ObservableObject` です。 作成後に塗りつぶし領域の形状、スタイル、穴、Z インデックス、または選択状態が変化する場合に使用してください。 @@ -26,25 +29,12 @@ head: 透明度を表現するには `fillColor` または `strokeColor` のアルファ値を使用してください。 -```swift -let polygonState = PolygonState( - points: outerRing, - fillColor: UIColor.green.withAlphaComponent(0.25), - holes: [innerRing] -) - -Polygon(state: polygonState) - -polygonState.strokeWidth = 3 -polygonState.fillColor = UIColor.red.withAlphaComponent(0.2) -``` + ## 穴の更新 `holes` は外側のポリゴンの内部にあるリングの配列です: -```swift -polygonState.holes = [restrictedAreaRing] -``` + 半透明な領域には `fillColor` のアルファ値を使用してください。`points` と `holes` はデータモデルで要求される座標順序に従って設定してください。 diff --git a/docs/src/content/docs/ja/states/polyline-state.mdx b/docs/src/content/docs/ja/states/polyline-state.mdx index a7b0d47..62f8b2c 100644 --- a/docs/src/content/docs/ja/states/polyline-state.mdx +++ b/docs/src/content/docs/ja/states/polyline-state.mdx @@ -7,6 +7,9 @@ head: content: PolylineState — MapConductor iOS SDKでポリラインの頂点とストロークを動的に更新する方法 --- +import PolylineStateBasicExample from '~/components/states/polyline-state/PolylineStateBasicExample.astro'; +import PolylineStateRouteUpdateExample from '~/components/states/polyline-state/PolylineStateRouteUpdateExample.astro'; + `PolylineState` は `Polyline(state:)` で使用される `ObservableObject` です。 オーバーレイの宣言を再作成せずに、ルート、トラック、またはラインスタイルを更新する必要がある場合に使用してください。 @@ -21,29 +24,12 @@ head: - `extra: Any?` - `onClick: OnPolylineEventHandler?` -```swift -let polylineState = PolylineState( - points: [ - GeoPoint(latitude: 35.6812, longitude: 139.7671), - GeoPoint(latitude: 35.6586, longitude: 139.7454) - ], - strokeColor: .blue, - strokeWidth: 4 -) - -Polyline(state: polylineState) - -polylineState.points.append(GeoPoint(latitude: 35.7101, longitude: 139.8107)) -polylineState.strokeColor = .red -``` + ## ルートの更新 ルートが再計算された場合は、points 配列全体を置き換えてください: -```swift -polylineState.points = recalculatedRoute -polylineState.strokeWidth = 5 -``` + ユーザーがルートを選択したり、`extra` に格納したラインのメタデータを確認できるようにする場合は `onClick` を使用してください。 diff --git a/docs/src/content/docs/mapviewholder/arcgis-2d.mdx b/docs/src/content/docs/mapviewholder/arcgis-2d.mdx new file mode 100644 index 0000000..819edfa --- /dev/null +++ b/docs/src/content/docs/mapviewholder/arcgis-2d.mdx @@ -0,0 +1,60 @@ +--- +title: ArcGISMapView2D (2D Flat View) +head: + - tag: meta + attrs: + name: description + content: ArcGISMapView2D — ArcGIS 2D flat map view in MapConductor iOS SDK. +--- +import ArcGIS2DBasicUsage from '~/components/mapviewholder/arcgis-2d/ArcGIS2DBasicUsage.astro'; +import ArcGIS2DInitializerParams from '~/components/mapviewholder/arcgis-2d/ArcGIS2DInitializerParams.astro'; +import ArcGIS2DMapHolderExample from '~/components/mapviewholder/arcgis-2d/ArcGIS2DMapHolderExample.astro'; + +`ArcGISMapView2D` is a SwiftUI view that displays an ArcGIS 2D flat map. While the standard `ArcGISMapView` is based on a 3D `SceneView`, `ArcGISMapView2D` uses the ArcGIS 2D `MapView`. + +## Overview + +Use this when you need an ArcGIS map specialized for 2D display. `ArcGISMapViewState` is shared with the standard `ArcGISMapView` — no separate state type is needed. + +`ArcGISMapView2D` is provided by the `MapConductorForArcGIS` module. + +## Differences from ArcGISMapView + +| | `ArcGISMapView` | `ArcGISMapView2D` | +|---|---|---| +| Native view | 3D `SceneView` | 2D `MapView` | +| Tilt support | Yes | No (always `0`) | +| 3D buildings | Yes | No | +| State type | `ArcGISMapViewState` | `ArcGISMapViewState` (shared) | + +- Uses a 2D `MapView` instead of a 3D `SceneView` +- `tilt` is always fixed at `0` — 3D perspective is not available +- Does not render 3D buildings or elevation +- Lighter for screens that do not need 3D display +- `ArcGISMapViewState` can be shared with `ArcGISMapView` + +## Basic Usage + + + +## Initializer Parameters + + + +- **`state`**: `ArcGISMapViewState`. Holds the camera position, map design, controller, and other state. +- **`onMapLoaded`**: Called when the map has finished loading. +- **`onMapClick`**: Called when the user taps a part of the map that is not an overlay. +- **`onMapLongClick`**: Called when the user long-presses a part of the map that is not an overlay. +- **`onCameraMoveStart`**: Called when camera movement starts. +- **`onCameraMove`**: Called while the camera is moving. +- **`onCameraMoveEnd`**: Called when camera movement ends. +- **`sdkInitialize`**: A closure used to perform ArcGIS SDK initialization (API key setup). Called once per process. +- **`content`**: A `@MapViewContentBuilder` closure for placing map overlays such as markers, polylines, and polygons. + +## Notes + +In `ArcGISMapView2D`, `tilt` is always fixed at `0`. Even if `tilt` is specified in `MapCameraPosition`, the 2D `MapView` ignores it and does not render 3D buildings or a 3D perspective. + +For basemap changes that can be represented by `ArcGISDesign`, update `mapViewState.mapDesignType` instead of accessing the native SDK directly. To access the native `ArcGIS.Map`, cast `holder.map`: + + diff --git a/docs/src/content/docs/mapviewholder/arcgis.mdx b/docs/src/content/docs/mapviewholder/arcgis.mdx new file mode 100644 index 0000000..978b04d --- /dev/null +++ b/docs/src/content/docs/mapviewholder/arcgis.mdx @@ -0,0 +1,20 @@ +--- +title: ArcGIS MapViewHolder +head: + - tag: meta + attrs: + name: description + content: ArcGISMapViewHolder — access the native ArcGIS map instance in MapConductor iOS SDK. +--- +import ArcGISSceneHolderExample from '~/components/mapviewholder/arcgis/ArcGISSceneHolderExample.astro'; +import ArcGISMapHolderExample from '~/components/mapviewholder/arcgis/ArcGISMapHolderExample.astro'; + +`ArcGISMapViewState.getMapViewHolder()` returns `AnyMapViewHolder?`. With `ArcGISMapView` (3D), `holder.map` can be cast to `ArcGIS.Scene`; with `ArcGISMapView2D` (2D), `holder.map` can be cast to `ArcGIS.Map`. + + + +To access the native `Map` from a 2D map: + + + +For basemap changes that can be represented by `ArcGISDesign`, update `mapViewState.mapDesignType` instead of accessing the native SDK directly. diff --git a/docs/src/content/docs/mapviewholder/googlemaps.mdx b/docs/src/content/docs/mapviewholder/googlemaps.mdx index 56a61a4..0c54016 100644 --- a/docs/src/content/docs/mapviewholder/googlemaps.mdx +++ b/docs/src/content/docs/mapviewholder/googlemaps.mdx @@ -6,16 +6,10 @@ head: name: description content: GoogleMapViewHolder — access the native Google Maps instance in MapConductor iOS SDK. --- +import GoogleMapsHolderExample from '~/components/mapviewholder/googlemaps/GoogleMapsHolderExample.astro'; `GoogleMapViewState.getMapViewHolder()` returns `AnyMapViewHolder?`. The native Google Maps object is stored as `Any` and can be cast to `GMSMapView`. -```swift -import GoogleMaps - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? GMSMapView { - mapView.mapType = .hybrid -} -``` + Use `toScreenOffset(position:)` and `fromScreenOffset(offset:)` for coordinate conversion without casting to the native SDK type. diff --git a/docs/src/content/docs/mapviewholder/here-maps.mdx b/docs/src/content/docs/mapviewholder/here-maps.mdx new file mode 100644 index 0000000..cd54163 --- /dev/null +++ b/docs/src/content/docs/mapviewholder/here-maps.mdx @@ -0,0 +1,15 @@ +--- +title: HERE Maps MapViewHolder +head: + - tag: meta + attrs: + name: description + content: HereMapViewHolder — access the native HERE map instance in MapConductor iOS SDK. +--- +import HereMapsHolderExample from '~/components/mapviewholder/here-maps/HereMapsHolderExample.astro'; + +`HereMapViewState.getMapViewHolder()` returns `AnyMapViewHolder?`. The native view can be cast to HERE SDK's `MapView`; `holder.map` can be cast to `MapScene`. + + + +For style changes that can be represented by `HereMapDesign`, update `mapViewState.mapDesignType` instead of accessing the native SDK directly. diff --git a/docs/src/content/docs/mapviewholder/index.mdx b/docs/src/content/docs/mapviewholder/index.mdx index dbd5f9e..5f0b5eb 100644 --- a/docs/src/content/docs/mapviewholder/index.mdx +++ b/docs/src/content/docs/mapviewholder/index.mdx @@ -6,18 +6,15 @@ head: name: description content: MapViewHolder — access the underlying native map instance in MapConductor iOS SDK. --- +import ScreenOffsetExample from '~/components/mapviewholder/index/ScreenOffsetExample.astro'; +import CastMapViewExample from '~/components/mapviewholder/index/CastMapViewExample.astro'; +import GuardHolderExample from '~/components/mapviewholder/index/GuardHolderExample.astro'; `MapViewStateProtocol.getMapViewHolder()` returns `AnyMapViewHolder?`. Use the holder as an escape hatch for native SDK features that are not part of the MapConductor common API. It is not required for normal marker, shape, camera, or event handling. -```swift -if let holder = mapViewState.getMapViewHolder() { - let screenPoint = holder.toScreenOffset( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671) - ) -} -``` + `AnyMapViewHolder` exposes: @@ -29,23 +26,12 @@ if let holder = mapViewState.getMapViewHolder() { Provider-specific holder classes such as `GoogleMapViewHolder` are internal implementation details in the current source. If you need native SDK access, cast `holder.mapView` or `holder.map` to the native SDK type. -```swift -import GoogleMaps - -if let holder = mapViewState.getMapViewHolder(), - let googleMap = holder.mapView as? GMSMapView { - googleMap.mapType = .hybrid -} -``` + ## Readiness The holder may be `nil` before the native map is ready. Access it from a point in your UI where the map has been created, and always guard the optional value: -```swift -guard let holder = mapViewState.getMapViewHolder() else { - return -} -``` + For portable features, prefer MapConductor APIs. For provider-only behavior, keep casts close to the provider-specific screen so the rest of your map code remains shared. diff --git a/docs/src/content/docs/mapviewholder/mapbox.mdx b/docs/src/content/docs/mapviewholder/mapbox.mdx index 617e155..e2fcc3b 100644 --- a/docs/src/content/docs/mapviewholder/mapbox.mdx +++ b/docs/src/content/docs/mapviewholder/mapbox.mdx @@ -6,19 +6,8 @@ head: name: description content: MapboxMapViewHolder — access the native Mapbox map instance in MapConductor iOS SDK. --- +import MapboxHolderExample from '~/components/mapviewholder/mapbox/MapboxHolderExample.astro'; `MapboxViewState.getMapViewHolder()` returns `AnyMapViewHolder?`. The native view can be cast to Mapbox's `MapView`; `holder.map` can be cast to `MapboxMap`. -```swift -import MapboxMaps - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? MapView { - mapView.mapboxMap.setCamera( - to: CameraOptions( - center: CLLocationCoordinate2D(latitude: 35.6812, longitude: 139.7671), - zoom: 14 - ) - ) -} -``` + diff --git a/docs/src/content/docs/mapviewholder/mapkit.mdx b/docs/src/content/docs/mapviewholder/mapkit.mdx index f6bfc52..8e84d40 100644 --- a/docs/src/content/docs/mapviewholder/mapkit.mdx +++ b/docs/src/content/docs/mapviewholder/mapkit.mdx @@ -6,20 +6,13 @@ head: name: description content: MapKitMapViewHolder — access the native MapKit instance in MapConductor iOS SDK. --- +import MapKitHolderExample from '~/components/mapviewholder/mapkit/MapKitHolderExample.astro'; +import MapKitDesignTypeExample from '~/components/mapviewholder/mapkit/MapKitDesignTypeExample.astro'; `MapKitViewState.getMapViewHolder()` returns `AnyMapViewHolder?`. The native MapKit object can be cast to `MKMapView`. -```swift -import MapKit - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? MKMapView { - mapView.showsTraffic = true -} -``` + Map type changes that are covered by the SDK should use `mapDesignType` instead: -```swift -mapViewState.mapDesignType = MapKitMapDesign.Hybrid -``` + diff --git a/docs/src/content/docs/mapviewholder/maplibre.mdx b/docs/src/content/docs/mapviewholder/maplibre.mdx index 84aced9..2b6192e 100644 --- a/docs/src/content/docs/mapviewholder/maplibre.mdx +++ b/docs/src/content/docs/mapviewholder/maplibre.mdx @@ -6,18 +6,8 @@ head: name: description content: MapLibreMapViewHolder — access the native MapLibre instance in MapConductor iOS SDK. --- +import MapLibreHolderExample from '~/components/mapviewholder/maplibre/MapLibreHolderExample.astro'; `MapLibreViewState.getMapViewHolder()` returns `AnyMapViewHolder?`. The native MapLibre object can be cast to `MLNMapView`. -```swift -import MapLibre - -if let holder = mapViewState.getMapViewHolder(), - let mapView = holder.mapView as? MLNMapView { - mapView.setCenter( - CLLocationCoordinate2D(latitude: 35.6812, longitude: 139.7671), - zoomLevel: 14, - animated: true - ) -} -``` + diff --git a/docs/src/content/docs/modules.mdx b/docs/src/content/docs/modules.mdx index 33a0917..e88961a 100644 --- a/docs/src/content/docs/modules.mdx +++ b/docs/src/content/docs/modules.mdx @@ -7,6 +7,9 @@ head: content: MapConductor iOS SDK module overview — Swift Package Manager products for each map provider. --- +import ModulesPackageSwiftExample from '~/components/modules/ModulesPackageSwiftExample.astro'; +import ModulesOptionalPackagesExample from '~/components/modules/ModulesOptionalPackagesExample.astro'; + The repository is split into independent Swift packages so each application can depend only on the providers and utilities it actually uses. A typical app depends on `MapConductorCore` plus one provider module. | Package repository | Product | Main types | @@ -17,8 +20,10 @@ The repository is split into independent Swift packages so each application can | `ios-for-mapkit` | `MapConductorForMapKit` | `MapKitViewState`, `MapKitMapView`, `MapKitMapDesign` | | `ios-for-maplibre` | `MapConductorForMapLibre` | `MapLibreViewState`, `MapLibreMapView`, `MapLibreDesign` | | `ios-for-arcgis` | `MapConductorForArcGIS` | `ArcGISMapViewState`, `ArcGISMapView`, `ArcGISMapDesign` | +| `ios-for-here` | `MapConductorForHERE` | `HereMapViewState`, `HereMapView`, `HereMapDesign` | | `ios-heatmap` | `MapConductorHeatmap` | `HeatmapOverlay`, `HeatmapOverlayState`, `HeatmapPointView`, `HeatmapPoints` | | `ios-marker-cluster` | `MapConductorMarkerCluster` | `MarkerClusterGroup`, `MarkerClusterGroupState`, `MarkerClusterStrategy` | +| `ios-geojson-layer` | `MapConductorGeoJSON` | `GeoJSONLayerState`, `GeoJSONLayer`, `GeoJSONFeature` | ## Core Package @@ -46,43 +51,12 @@ Use optional packages only on screens that need those features. They integrate w - `MapConductorHeatmap` adds tile-based heatmap rendering with `HeatmapOverlay`, `HeatmapPointView`, `HeatmapPoints`, and `HeatmapOverlayState`. - `MapConductorMarkerCluster` adds marker clustering with `MarkerClusterGroup`, `MarkerClusterGroupState`, and `MarkerClusterStrategy`. +- `MapConductorGeoJSON` adds tile-based GeoJSON layer rendering with `GeoJSONLayer`, `GeoJSONLayerState`, and `GeoJSONFeature`. ## Package.swift Example -```swift -dependencies: [ - .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}"), -], -targets: [ - .target( - name: "App", - dependencies: [ - .product(name: "MapConductorCore", package: "ios-sdk-core"), - .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), - ] - ) -] -``` + ## Example with Optional Modules -```swift -dependencies: [ - .package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-heatmap", from: "{HEATMAP_MODULE_VERSION}"), - .package(url: "https://github.com/MapConductor/ios-marker-cluster", from: "{MARKER_CLUSTERING_MODULE_VERSION}"), -], -targets: [ - .target( - name: "App", - dependencies: [ - .product(name: "MapConductorCore", package: "ios-sdk-core"), - .product(name: "MapConductorForGoogleMaps", package: "ios-for-googlemaps"), - .product(name: "MapConductorHeatmap", package: "ios-heatmap"), - .product(name: "MapConductorMarkerCluster", package: "ios-marker-cluster"), - ] - ) -] -``` + diff --git a/docs/src/content/docs/setup/arcgis.mdx b/docs/src/content/docs/setup/arcgis.mdx new file mode 100644 index 0000000..47fbcda --- /dev/null +++ b/docs/src/content/docs/setup/arcgis.mdx @@ -0,0 +1,116 @@ +--- +title: ArcGIS Setup +head: + - tag: meta + attrs: + name: description + content: Set up ArcGIS as a map provider in MapConductor iOS SDK — step-by-step integration guide. +--- + +import ArcGISInitExample from '~/components/setup/arcgis/ArcGISInitExample.astro'; +import ArcGISInitSdkInitializeExample from '~/components/setup/arcgis/ArcGISInitSdkInitializeExample.astro'; +import ArcGISVerification3DExample from '~/components/setup/arcgis/ArcGISVerification3DExample.astro'; +import ArcGISVerification2DExample from '~/components/setup/arcgis/ArcGISVerification2DExample.astro'; +import ArcGISStylePresetsExample from '~/components/setup/arcgis/ArcGISStylePresetsExample.astro'; +import ArcGISCustomDesignExample from '~/components/setup/arcgis/ArcGISCustomDesignExample.astro'; + +This section covers the setup process for ArcGIS Maps SDK for Swift integration with MapConductor. + +Use this provider when your app already depends on ArcGIS Maps SDK for Swift, needs ArcGIS basemaps through `ArcGISDesign`, or needs ArcGIS 3D and 2D map views. + +## Prerequisites + +- iOS development environment (Xcode 15+) +- ArcGIS developer account +- ArcGIS API key +- iOS deployment target set to 17.0 or higher + +## Setup Steps + +### 1. ArcGIS Configuration + +1. Go to [ArcGIS Location Platform](https://location.arcgis.com/) or [ArcGIS Developers](https://developers.arcgis.com/) +2. Create an API key for your project or app +3. Enable the ArcGIS services you need, such as basemaps, geocoding, or routing +4. Restrict the API key for your app's usage + +### 2. Swift Package Manager Configuration + +Add MapConductorForArcGIS to your Xcode project: + +1. In Xcode, go to **File > Add Package Dependencies** +2. Enter the package URL: + - `https://github.com/MapConductor/ios-for-arcgis` + +ArcGIS Maps SDK for Swift is automatically installed as a dependency — no additional packages are required. + +### 3. Initialize ArcGIS in Your App + +Call `arcGISApiKeyInitialize(apiKey:)` once before any map view is displayed. The recommended place is your `@main` App struct: + + + +Alternatively, pass the call via the `sdkInitialize` parameter on `ArcGISMapView` or `ArcGISMapView2D`: + + + +## Verification + +Build and run your app with the following code to verify the 3D map setup: + + + +To display a 2D map, pass the same `ArcGISMapViewState` to `ArcGISMapView2D`: + + + +If the map displays without errors, your setup is working. Test basic interactions (zoom, pan, and tilt in the 3D view) to confirm everything is functioning correctly. + +## Troubleshooting + +### Common Issues + +**Map not displaying (blank screen)** + +- Verify the API key passed to `arcGISApiKeyInitialize(apiKey:)` is correct +- Check that `arcGISApiKeyInitialize(apiKey:)` is called before any map view is created +- Ensure the required ArcGIS services are enabled for the API key +- Verify the iOS deployment target is 17.0 or higher + +**API key errors** + +- Double-check the API key value +- Ensure the key has not been revoked +- Confirm the key restrictions match your app's bundle ID and enabled services + +**Build errors** + +- Ensure `MapConductorForArcGIS` is correctly linked to your target +- Update Swift Package Manager dependencies +- Clear the build folder (Cmd+Shift+K) +- Delete derived data: `~/Library/Developer/Xcode/DerivedData` + +### Location Services + +To enable location-based features, add the following key to `Info.plist`: + +```xml +NSLocationWhenInUseUsageDescription +We need your location to display on the map +``` + +## Map Styles + +Use `ArcGISDesign` static presets to set the map style: + + + +To create a design from a known ID, use `ArcGISDesign.Create(id:)`: + + + +## Next Steps + +Once ArcGIS Maps SDK for Swift is properly configured, you can use `ArcGISMapView` or `ArcGISMapView2D` as described in the [Map View component documentation](/components/mapviewcomponent/). + +For more examples, see the [Get Started](/get-started/) tutorial. diff --git a/docs/src/content/docs/setup/google-maps.mdx b/docs/src/content/docs/setup/google-maps.mdx index 33740b5..5ea07e8 100644 --- a/docs/src/content/docs/setup/google-maps.mdx +++ b/docs/src/content/docs/setup/google-maps.mdx @@ -7,6 +7,11 @@ head: content: Set up Google Maps as a map provider in MapConductor iOS SDK — step-by-step integration guide. --- +import GoogleMapsInitExample from '~/components/setup/google-maps/GoogleMapsInitExample.astro'; +import GoogleMapsInitSdkInitializeExample from '~/components/setup/google-maps/GoogleMapsInitSdkInitializeExample.astro'; +import GoogleMapsVerificationExample from '~/components/setup/google-maps/GoogleMapsVerificationExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + This section covers the setup process for Google Maps SDK integration with MapConductor. Use this provider when your app already depends on Google Maps SDK for iOS or needs Google map types through `GoogleMapDesign`. @@ -41,33 +46,11 @@ Add the Google Maps SDK and MapConductor to your Xcode project: Call `GMSServices.provideAPIKey(_:)` once before any map view is displayed. The recommended place is your `@main` App struct: -```swift -import SwiftUI -import GoogleMaps -import MapConductorForGoogleMaps - -@main -struct MyApp: App { - init() { - GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` + Alternatively, pass the call via the `sdkInitialize` parameter on `GoogleMapView`: -```swift -GoogleMapView( - state: mapState, - sdkInitialize: { GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY") } -) { ... } -``` + ### 4. Add Location Permissions (if needed) @@ -82,25 +65,7 @@ If your app uses location services with Google Maps, add to `Info.plist`: Build and run your app with the following code to verify the setup: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject var mapState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 37.7749, longitude: -122.4194), - zoom: 12.0 - ) - ) - - var body: some View { - GoogleMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + If the map displays without errors, your setup is working. Test basic interactions (zoom, pan) to confirm everything is functioning correctly. @@ -137,19 +102,7 @@ If you need location-based features: 1. Add location permissions to Info.plist (see step 4) 2. Request user permission in your app: -```swift -import CoreLocation - -class LocationManager: NSObject, CLLocationManagerDelegate { - let manager = CLLocationManager() - - override init() { - super.init() - manager.delegate = self - manager.requestWhenInUseAuthorization() - } -} -``` + ## Next Steps diff --git a/docs/src/content/docs/setup/here-maps.mdx b/docs/src/content/docs/setup/here-maps.mdx new file mode 100644 index 0000000..12cbe3c --- /dev/null +++ b/docs/src/content/docs/setup/here-maps.mdx @@ -0,0 +1,104 @@ +--- +title: HERE Maps Setup +head: + - tag: meta + attrs: + name: description + content: Set up HERE Maps as a map provider in MapConductor iOS SDK — step-by-step integration guide. +--- + +import HereMapsInitExample from '~/components/setup/here-maps/HereMapsInitExample.astro'; +import HereMapsInitSdkInitializeExample from '~/components/setup/here-maps/HereMapsInitSdkInitializeExample.astro'; +import HereMapsVerificationExample from '~/components/setup/here-maps/HereMapsVerificationExample.astro'; +import HereMapsStylePresetsExample from '~/components/setup/here-maps/HereMapsStylePresetsExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + +This section covers the setup process for HERE SDK integration with MapConductor. + +Use this provider when your app already depends on HERE SDK or needs HERE map schemes through `HereMapDesign`. + +## Prerequisites + +- iOS development environment (Xcode 15+) +- HERE Platform account +- HERE SDK access key ID and access key secret +- iOS deployment target set to 16.0 or higher + +## Setup Steps + +### 1. HERE Platform Configuration + +1. Go to [HERE Platform](https://platform.here.com/) +2. Create a project or select an existing project +3. Create an access key for HERE SDK +4. Copy the access key ID and access key secret + +### 2. Swift Package Manager Configuration + +Add MapConductorForHERE to your Xcode project: + +1. In Xcode, go to **File > Add Package Dependencies** +2. Enter the package URL: + - `https://github.com/MapConductor/ios-for-here` + +### 3. Initialize HERE SDK in Your App + +Call `initializeHERE(accessKeyId:accessKeySecret:)` once before any map view is displayed. The recommended place is your `@main` App struct: + + + +Alternatively, pass the call via the `sdkInitialize` parameter on `HereMapView`: + + + +### 4. Add Location Permissions (If Needed) + +If your app uses location services with HERE SDK, add this to `Info.plist`: + +```xml +NSLocationWhenInUseUsageDescription +We need your location to display on the map +``` + +## Verification + +Build and run your app with the following code to verify the setup: + + + +If the map displays without errors, your setup is working. Test basic interactions (zoom, pan) to confirm everything is functioning correctly. + +## Troubleshooting + +### Common Issues + +**Map not displaying (blank screen)** + +- Verify the access key ID and access key secret passed to `initializeHERE(accessKeyId:accessKeySecret:)` are correct +- Check that `initializeHERE(accessKeyId:accessKeySecret:)` is called before any map view is created +- Verify the iOS deployment target is 16.0 or higher +- Ensure `MapConductorForHERE` is correctly linked to your target + +**HERE SDK initialization errors** + +- Double-check the access key ID and access key secret values +- Ensure the key has not been disabled or deleted +- Catch the thrown initialization error and log the error details + +### Location Services + +To enable location-based features, add the location permission to `Info.plist` (see step 4) and request authorization in your app: + + + +## Map Styles + +Use `HereMapDesign` static presets to set the map style: + + + +## Next Steps + +Once HERE SDK is properly configured, you can use `HereMapView` as described in the [Map View component documentation](/components/mapviewcomponent/). + +For more examples, see the [Get Started](/get-started/) tutorial. diff --git a/docs/src/content/docs/setup/index.mdx b/docs/src/content/docs/setup/index.mdx index 8025052..b495640 100644 --- a/docs/src/content/docs/setup/index.mdx +++ b/docs/src/content/docs/setup/index.mdx @@ -7,12 +7,12 @@ head: content: MapConductor iOS SDK setup — Swift Package Manager installation for each supported map provider. --- +import PackageExample from '~/components/setup/overview/PackageExample.astro'; +import MinimalGoogleMapsExample from '~/components/setup/overview/MinimalGoogleMapsExample.astro'; + Use `MapConductorCore` plus one or more provider packages. -```swift -.package(url: "https://github.com/MapConductor/ios-sdk-core", from: "{CORE_MODULE_VERSION}"), -.package(url: "https://github.com/MapConductor/ios-for-googlemaps", from: "{GOOGLEMAPS_MODULE_VERSION}") -``` + Provider SDK setup still applies: @@ -21,31 +21,10 @@ Provider SDK setup still applies: - MapKit: no global SDK initialization - MapLibre: choose a `MapLibreDesign` - ArcGIS: use the authentication helpers in `MapConductorForArcGIS` when authentication is needed +- HERE Maps: provide your access key ID and secret when initializing the HERE SDK ## Minimal Google Maps Example -```swift -import SwiftUI -import MapConductorCore -import MapConductorForGoogleMaps - -struct ContentView: View { - @StateObject private var mapViewState = GoogleMapViewState( - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 13 - ) - ) - - var body: some View { - GoogleMapView(state: mapViewState) { - Marker( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - icon: DefaultMarkerIcon(label: "T") - ) - } - } -} -``` + See the provider-specific setup pages for package and credential details. diff --git a/docs/src/content/docs/setup/mapbox.mdx b/docs/src/content/docs/setup/mapbox.mdx index bbafb75..01fb6ed 100644 --- a/docs/src/content/docs/setup/mapbox.mdx +++ b/docs/src/content/docs/setup/mapbox.mdx @@ -7,6 +7,12 @@ head: content: Set up Mapbox as a map provider in MapConductor iOS SDK — step-by-step integration guide. --- +import MapboxInitExample from '~/components/setup/mapbox/MapboxInitExample.astro'; +import MapboxInitSdkInitializeExample from '~/components/setup/mapbox/MapboxInitSdkInitializeExample.astro'; +import MapboxVerificationExample from '~/components/setup/mapbox/MapboxVerificationExample.astro'; +import MapboxStylePresetsExample from '~/components/setup/mapbox/MapboxStylePresetsExample.astro'; +import MapboxCustomStyleExample from '~/components/setup/mapbox/MapboxCustomStyleExample.astro'; + This section covers the setup process for Mapbox SDK integration with MapConductor. Use this provider when your app already depends on Mapbox Maps SDK or needs Mapbox style URLs through `MapboxMapDesign`. @@ -42,57 +48,17 @@ required. Call `initializeMapbox(accessToken:)` once before any map view is displayed. The recommended place is your `@main` App struct: -```swift -import SwiftUI -import MapConductorForMapbox - -@main -struct MyApp: App { - init() { - initializeMapbox(accessToken: "YOUR_MAPBOX_PUBLIC_TOKEN") - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` + Alternatively, pass the call via the `sdkInitialize` parameter on `MapboxMapView`: -```swift -MapboxMapView( - state: mapState, - sdkInitialize: { initializeMapbox(accessToken: "YOUR_MAPBOX_PUBLIC_TOKEN") } -) { ... } -``` + ## Verification Build and run your app with the following code to verify the setup: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForMapbox - -struct ContentView: View { - @StateObject var mapState = MapboxViewState( - mapDesignType: MapboxMapDesign.Standard, - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 37.7749, longitude: -122.4194), - zoom: 12.0 - ) - ) - - var body: some View { - MapboxMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + If the map displays without errors, your setup is working. Test basic interactions (zoom, pan) to confirm everything is functioning correctly. @@ -128,25 +94,11 @@ To enable location-based features, add the following key to `Info.plist`: Use `MapboxMapDesign` static presets to set the map style: -```swift -// Standard style (default) -mapState.mapDesignType = MapboxMapDesign.Standard - -// Streets style -mapState.mapDesignType = MapboxMapDesign.Streets - -// Satellite imagery with roads and labels -mapState.mapDesignType = MapboxMapDesign.SatelliteStreets - -// Dark style -mapState.mapDesignType = MapboxMapDesign.Dark -``` + For a custom style, use `MapboxMapDesign.custom(styleURI:)`: -```swift -mapState.mapDesignType = MapboxMapDesign.custom(styleURI: "mapbox://styles/user/abc123") -``` + ## Next Steps diff --git a/docs/src/content/docs/setup/mapkit.mdx b/docs/src/content/docs/setup/mapkit.mdx index 97ecf7e..7308bf5 100644 --- a/docs/src/content/docs/setup/mapkit.mdx +++ b/docs/src/content/docs/setup/mapkit.mdx @@ -7,6 +7,10 @@ head: content: Set up Apple MapKit as a map provider in MapConductor iOS SDK — step-by-step integration guide. --- +import MapKitVerificationExample from '~/components/setup/mapkit/MapKitVerificationExample.astro'; +import MapKitStylePresetsExample from '~/components/setup/mapkit/MapKitStylePresetsExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + This section covers the setup process for MapKit integration with MapConductor. Use this provider when you want Apple's built-in map SDK with no external dependencies or API @@ -42,31 +46,7 @@ If your app uses location services, add the following key to `Info.plist`: Build and run your app with the following code to verify the setup: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForMapKit - -struct ContentView: View { - @StateObject var mapState = MapKitViewState( - mapDesignType: MapKitMapDesign.Standard, - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 12.0 - ) - ) - - var body: some View { - MapKitMapView( - state: mapState, - sdkInitialize: { - // Optional. Called once before MKMapView is created. - } - ) - .ignoresSafeArea() - } -} -``` + If the map displays without errors, your setup is working. Test basic interactions (zoom, pan) to confirm everything is functioning correctly. @@ -91,37 +71,13 @@ to confirm everything is functioning correctly. To enable location-based features, add location permissions to `Info.plist` (see step 2) and request authorization in your app: -```swift -import CoreLocation - -class LocationManager: NSObject, CLLocationManagerDelegate { - let manager = CLLocationManager() - - override init() { - super.init() - manager.delegate = self - manager.requestWhenInUseAuthorization() - } -} -``` + ## Map Styles Use `MapKitMapDesign` static presets to set the map style: -```swift -// Standard street map -mapState.mapDesignType = MapKitMapDesign.Standard - -// Satellite imagery -mapState.mapDesignType = MapKitMapDesign.Satellite - -// Satellite with roads and labels -mapState.mapDesignType = MapKitMapDesign.Hybrid - -// Muted standard style -mapState.mapDesignType = MapKitMapDesign.MutedStandard -``` + ## Next Steps diff --git a/docs/src/content/docs/setup/maplibre.mdx b/docs/src/content/docs/setup/maplibre.mdx index 6a07bf2..854b6fc 100644 --- a/docs/src/content/docs/setup/maplibre.mdx +++ b/docs/src/content/docs/setup/maplibre.mdx @@ -7,6 +7,11 @@ head: content: Set up MapLibre as a map provider in MapConductor iOS SDK — step-by-step integration guide. --- +import MapLibreVerificationExample from '~/components/setup/maplibre/MapLibreVerificationExample.astro'; +import MapLibreStylePresetsExample from '~/components/setup/maplibre/MapLibreStylePresetsExample.astro'; +import MapLibreCustomStyleExample from '~/components/setup/maplibre/MapLibreCustomStyleExample.astro'; +import LocationManagerExample from '~/components/setup/shared/LocationManagerExample.astro'; + This section covers the setup process for MapLibre SDK integration with MapConductor. Use this provider when you need an open-source map renderer without a proprietary API key, or @@ -47,26 +52,7 @@ If your app uses location services, add the following key to `Info.plist`: Build and run your app with the following code to verify the setup: -```swift -import SwiftUI -import MapConductorCore -import MapConductorForMapLibre - -struct ContentView: View { - @StateObject var mapState = MapLibreViewState( - mapDesignType: MapLibreDesign.OsmBright, - cameraPosition: MapCameraPosition( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - zoom: 12.0 - ) - ) - - var body: some View { - MapLibreMapView(state: mapState) - .ignoresSafeArea() - } -} -``` + If the map displays without errors, your setup is working. Test basic interactions (zoom, pan) to confirm everything is functioning correctly. @@ -93,47 +79,17 @@ to confirm everything is functioning correctly. To enable location-based features, add location permissions to `Info.plist` (see step 2) and request authorization in your app: -```swift -import CoreLocation - -class LocationManager: NSObject, CLLocationManagerDelegate { - let manager = CLLocationManager() - - override init() { - super.init() - manager.delegate = self - manager.requestWhenInUseAuthorization() - } -} -``` + ## Map Styles Use `MapLibreDesign` static presets to set the map style: -```swift -// OpenStreetMap Bright (English) -mapState.mapDesignType = MapLibreDesign.OsmBright - -// OpenStreetMap Bright (Japanese) -mapState.mapDesignType = MapLibreDesign.OsmBrightJa - -// MapTiler Toner (English) -mapState.mapDesignType = MapLibreDesign.MapTilerTonerEn - -// Lightweight demo tiles -mapState.mapDesignType = MapLibreDesign.DemoTiles -``` + For a custom style, construct a `MapLibreDesign` directly: -```swift -let custom = MapLibreDesign( - id: "custom", - styleJsonURL: "https://example.com/style.json" -) -mapState.mapDesignType = custom -``` + ## Next Steps diff --git a/docs/src/content/docs/states/circle-state.mdx b/docs/src/content/docs/states/circle-state.mdx index dd52b1c..d48e461 100644 --- a/docs/src/content/docs/states/circle-state.mdx +++ b/docs/src/content/docs/states/circle-state.mdx @@ -7,6 +7,9 @@ head: content: CircleState — dynamically update circle radius, fill color, and stroke in MapConductor iOS SDK. --- +import CircleStateBasicExample from '~/components/states/circle-state/CircleStateBasicExample.astro'; +import CircleStateSelectionExample from '~/components/states/circle-state/CircleStateSelectionExample.astro'; + `CircleState` is an `ObservableObject` used by `Circle(state:)`. Use it when the circle's center, radius, style, z-index, or click behavior changes after the map content is first rendered. @@ -27,27 +30,10 @@ Use it when the circle's center, radius, style, z-index, or click behavior chang Use `radiusMeters` for the circle size. Use alpha on `fillColor` or `strokeColor` for transparency. -```swift -let circleState = CircleState( - center: GeoPoint(latitude: 35.6812, longitude: 139.7671), - radiusMeters: 500, - fillColor: UIColor.blue.withAlphaComponent(0.2) -) - -Circle(state: circleState) - -circleState.radiusMeters = 1_000 -circleState.center = GeoPoint(latitude: 35.6895, longitude: 139.6917) -``` + ## Selection Example -```swift -circleState.onClick = { event in - selectedCircleId = event.state.id -} - -circleState.strokeColor = selectedCircleId == circleState.id ? .red : .blue -``` + Keep `id` stable when a circle represents a model object such as a geofence or search area. diff --git a/docs/src/content/docs/states/groundimage-state.mdx b/docs/src/content/docs/states/groundimage-state.mdx index f1a90df..7b0eed1 100644 --- a/docs/src/content/docs/states/groundimage-state.mdx +++ b/docs/src/content/docs/states/groundimage-state.mdx @@ -7,6 +7,9 @@ head: content: GroundImageState — dynamically update image bounds, opacity, and source in MapConductor iOS SDK. --- +import GroundImageStateBasicExample from '~/components/states/groundimage-state/GroundImageStateBasicExample.astro'; +import GroundImageStateRepositionExample from '~/components/states/groundimage-state/GroundImageStateRepositionExample.astro'; + `GroundImageState` is an `ObservableObject` used by `GroundImage(state:)`. Use it when the image, opacity, geographic bounds, or click handler changes at runtime. @@ -21,32 +24,12 @@ Use it when the image, opacity, geographic bounds, or click handler changes at r - `extra: Any?` - `onClick: OnGroundImageEventHandler?` -```swift -let bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.67, longitude: 139.74), - northEast: GeoPoint(latitude: 35.69, longitude: 139.76) -) - -let groundImageState = GroundImageState( - bounds: bounds, - image: UIImage(named: "overlay")!, - opacity: 0.5 -) - -GroundImage(state: groundImageState) - -groundImageState.opacity = 1.0 -``` + ## Repositioning Update `bounds` to move or resize the image: -```swift -groundImageState.bounds = GeoRectBounds( - southWest: GeoPoint(latitude: 35.66, longitude: 139.73), - northEast: GeoPoint(latitude: 35.70, longitude: 139.77) -) -``` + Update `image` when the displayed raster changes. Use `tileSize` to control the tile size used by the overlay renderer. diff --git a/docs/src/content/docs/states/marker-state.mdx b/docs/src/content/docs/states/marker-state.mdx index 5ae223f..7851ec3 100644 --- a/docs/src/content/docs/states/marker-state.mdx +++ b/docs/src/content/docs/states/marker-state.mdx @@ -7,6 +7,9 @@ head: content: MarkerState — dynamically update marker position, icon, and visibility in MapConductor iOS SDK. --- +import MarkerStateBasicExample from '~/components/states/marker-state/MarkerStateBasicExample.astro'; +import MarkerStateHandlersExample from '~/components/states/marker-state/MarkerStateHandlersExample.astro'; + `MarkerState` is an `ObservableObject` used by `Marker(state:)`. Use `MarkerState` when marker data needs to change after the marker is created. For static markers, the direct `Marker(position:...)` initializer is usually enough. @@ -28,22 +31,7 @@ Use `MarkerState` when marker data needs to change after the marker is created. Use `extra` for app-specific data and `icon` for visible marker text or imagery. -```swift -let markerState = MarkerState( - position: GeoPoint(latitude: 35.6812, longitude: 139.7671), - extra: "Tokyo Station", - icon: DefaultMarkerIcon(fillColor: .red, label: "T"), - draggable: true, - onClick: { state in - print("Clicked \(state.id), extra: \(String(describing: state.extra))") - } -) - -Marker(state: markerState) - -markerState.position = GeoPoint(latitude: 35.6895, longitude: 139.6917) -markerState.animate(.Bounce) -``` + Use `copy(...)` to create a new marker state with selected values changed. @@ -51,15 +39,7 @@ Use `copy(...)` to create a new marker state with selected values changed. Event handlers are stored on the state, so they can be replaced along with other marker properties: -```swift -markerState.onClick = { state in - selectedMarkerId = state.id -} - -markerState.onDragEnd = { state in - saveCoordinate(state.position) -} -``` + ## Identity diff --git a/docs/src/content/docs/states/polygon-state.mdx b/docs/src/content/docs/states/polygon-state.mdx index ae2fb82..9a0a384 100644 --- a/docs/src/content/docs/states/polygon-state.mdx +++ b/docs/src/content/docs/states/polygon-state.mdx @@ -9,6 +9,9 @@ markdown: - headingLinks: false --- +import PolygonStateBasicExample from '~/components/states/polygon-state/PolygonStateBasicExample.astro'; +import PolygonStateHolesExample from '~/components/states/polygon-state/PolygonStateHolesExample.astro'; + `PolygonState` is an `ObservableObject` used by `Polygon(state:)`. Use it when a filled area changes shape, style, holes, z-index, or selection state after creation. @@ -28,25 +31,12 @@ Use it when a filled area changes shape, style, holes, z-index, or selection sta Use alpha on `fillColor` or `strokeColor` for transparency. -```swift -let polygonState = PolygonState( - points: outerRing, - fillColor: UIColor.green.withAlphaComponent(0.25), - holes: [innerRing] -) - -Polygon(state: polygonState) - -polygonState.strokeWidth = 3 -polygonState.fillColor = UIColor.red.withAlphaComponent(0.2) -``` + ## Updating Holes `holes` is an array of rings inside the outer polygon: -```swift -polygonState.holes = [restrictedAreaRing] -``` + Use alpha on `fillColor` for translucent regions. Keep `points` and `holes` in the coordinate order required by your data model. diff --git a/docs/src/content/docs/states/polyline-state.mdx b/docs/src/content/docs/states/polyline-state.mdx index e77397c..824266c 100644 --- a/docs/src/content/docs/states/polyline-state.mdx +++ b/docs/src/content/docs/states/polyline-state.mdx @@ -7,6 +7,9 @@ head: content: PolylineState — dynamically update polyline points and stroke in MapConductor iOS SDK. --- +import PolylineStateBasicExample from '~/components/states/polyline-state/PolylineStateBasicExample.astro'; +import PolylineStateRouteUpdateExample from '~/components/states/polyline-state/PolylineStateRouteUpdateExample.astro'; + `PolylineState` is an `ObservableObject` used by `Polyline(state:)`. Use it when a route, track, or line style needs to update without recreating the overlay declaration. @@ -21,29 +24,12 @@ Use it when a route, track, or line style needs to update without recreating the - `extra: Any?` - `onClick: OnPolylineEventHandler?` -```swift -let polylineState = PolylineState( - points: [ - GeoPoint(latitude: 35.6812, longitude: 139.7671), - GeoPoint(latitude: 35.6586, longitude: 139.7454) - ], - strokeColor: .blue, - strokeWidth: 4 -) - -Polyline(state: polylineState) - -polylineState.points.append(GeoPoint(latitude: 35.7101, longitude: 139.8107)) -polylineState.strokeColor = .red -``` + ## Route Updates Replace the full points array when a route is recalculated: -```swift -polylineState.points = recalculatedRoute -polylineState.strokeWidth = 5 -``` + Use `onClick` when the user should be able to select a route or inspect line metadata stored in `extra`. diff --git a/ios-for-arcgis b/ios-for-arcgis index 7b780a0..7afcde4 160000 --- a/ios-for-arcgis +++ b/ios-for-arcgis @@ -1 +1 @@ -Subproject commit 7b780a091ca60a0b04ea6df49160ea5a6a541e28 +Subproject commit 7afcde4043c38ec9a9a3ed0ab6d4f1dc6fe2c754 diff --git a/ios-for-googlemaps b/ios-for-googlemaps index a2b44a9..194ce24 160000 --- a/ios-for-googlemaps +++ b/ios-for-googlemaps @@ -1 +1 @@ -Subproject commit a2b44a981e2ac9ebaabb4307a1fd04d183257809 +Subproject commit 194ce24bdc49d15f4591d6494932eb4146846a78 diff --git a/ios-for-here b/ios-for-here new file mode 160000 index 0000000..74af16d --- /dev/null +++ b/ios-for-here @@ -0,0 +1 @@ +Subproject commit 74af16d50c07247b9298b6018041880a09e38524 diff --git a/ios-for-longdo b/ios-for-longdo new file mode 160000 index 0000000..733c9b7 --- /dev/null +++ b/ios-for-longdo @@ -0,0 +1 @@ +Subproject commit 733c9b7755874d1a2584e038977de99f890b72b0 diff --git a/ios-for-mapbox b/ios-for-mapbox index 90f222d..cd75fc7 160000 --- a/ios-for-mapbox +++ b/ios-for-mapbox @@ -1 +1 @@ -Subproject commit 90f222d1d440b99bcb06ab9ceaf6b43b4d96d1a8 +Subproject commit cd75fc778ed9fd755c85d0f78af14aa8becc72c7 diff --git a/ios-for-mapkit b/ios-for-mapkit index dc9686c..8080b69 160000 --- a/ios-for-mapkit +++ b/ios-for-mapkit @@ -1 +1 @@ -Subproject commit dc9686ce7055aa3283501481d185ee8fd6e8f359 +Subproject commit 8080b69d489ce86771090dcf2b1a549b573887f2 diff --git a/ios-for-maplibre b/ios-for-maplibre index b32a416..7fe2cb2 160000 --- a/ios-for-maplibre +++ b/ios-for-maplibre @@ -1 +1 @@ -Subproject commit b32a4163f0927b8509ffe8a23977dadc9d5b8c22 +Subproject commit 7fe2cb21b74abc2c21946d99a6f689d5f56c433a diff --git a/ios-for-maptiler b/ios-for-maptiler new file mode 160000 index 0000000..61267ef --- /dev/null +++ b/ios-for-maptiler @@ -0,0 +1 @@ +Subproject commit 61267efc08bc5e2d0e17249f63113dca15946cc7 diff --git a/ios-for-tomtom b/ios-for-tomtom new file mode 160000 index 0000000..5c14494 --- /dev/null +++ b/ios-for-tomtom @@ -0,0 +1 @@ +Subproject commit 5c144949e7babdbab80491276e306061ecf26592 diff --git a/ios-geojson-layer b/ios-geojson-layer new file mode 160000 index 0000000..8ece631 --- /dev/null +++ b/ios-geojson-layer @@ -0,0 +1 @@ +Subproject commit 8ece631d12c86fd5788a632c8762539fad197069 diff --git a/ios-heatmap b/ios-heatmap index bc0a649..cce7deb 160000 --- a/ios-heatmap +++ b/ios-heatmap @@ -1 +1 @@ -Subproject commit bc0a649d3d1ccf23804855799441f9a16abe12cc +Subproject commit cce7debb2c1f29b8f27b7d08fb76d3b9658d0c94 diff --git a/ios-icons b/ios-icons new file mode 160000 index 0000000..1d94468 --- /dev/null +++ b/ios-icons @@ -0,0 +1 @@ +Subproject commit 1d94468d41e0a55e9264e50cd8e692263da7923d diff --git a/ios-marker-cluster b/ios-marker-cluster index b8fc3d5..e524811 160000 --- a/ios-marker-cluster +++ b/ios-marker-cluster @@ -1 +1 @@ -Subproject commit b8fc3d5a0346d0e364953446943f5c4b21556b81 +Subproject commit e5248113269736c506b8c3366bd29fbef6c723ea diff --git a/ios-sdk-core b/ios-sdk-core index cbfedd8..e6b2468 160000 --- a/ios-sdk-core +++ b/ios-sdk-core @@ -1 +1 @@ -Subproject commit cbfedd869e96d9b8d5f852a4939a4bcb3f18736e +Subproject commit e6b2468c75f10a96782ae22b58c43fb491434504 diff --git a/samples/MapConductorSampleApp/Debug.xcconfig b/samples/MapConductorSampleApp/Debug.xcconfig index 3891dd2..3852f2c 100644 --- a/samples/MapConductorSampleApp/Debug.xcconfig +++ b/samples/MapConductorSampleApp/Debug.xcconfig @@ -1,3 +1,6 @@ // Copy this file as Secrets.xcconfig, and set the following values GOOGLE_MAPS_API_KEY = xxxxxxxxxx MAPBOX_ACCESS_TOKEN = xxxxxxxxxx +ARCGIS_API_KEY = xxxxxxx +HERE_ACCESS_KEY_ID = xxxxxxx +HERE_ACCESS_KEY_SECRET = xxxxxxx diff --git a/samples/MapConductorSampleApp/Info.plist b/samples/MapConductorSampleApp/Info.plist index b5b690c..6fa26cd 100644 --- a/samples/MapConductorSampleApp/Info.plist +++ b/samples/MapConductorSampleApp/Info.plist @@ -20,6 +20,18 @@ $(GOOGLE_MAPS_API_KEY) MAPBOX_ACCESS_TOKEN $(MAPBOX_ACCESS_TOKEN) + ARCGIS_API_KEY + $(ARCGIS_API_KEY) + HERE_ACCESS_KEY_ID + $(HERE_ACCESS_KEY_ID) + HERE_ACCESS_KEY_SECRET + $(HERE_ACCESS_KEY_SECRET) + TOMTOM_API_KEY + $(TOMTOM_API_KEY) + MapTilerAPIKey + $(MAPTILER_API_KEY) + LONGDO_API_KEY + $(LONGDO_API_KEY) LSApplicationQueriesSchemes maps diff --git a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.pbxproj b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.pbxproj index c507f6c..912d19e 100644 --- a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.pbxproj +++ b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.pbxproj @@ -3,33 +3,45 @@ archiveVersion = 1; classes = { }; - objectVersion = 63; + objectVersion = 77; objects = { /* Begin PBXBuildFile section */ 0217B3005DD020C159AFEC21 /* FlyToMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8345A4E4E2060505B6AFAF /* FlyToMapPage.swift */; }; 029C92BC38D67AFD3EB2F8D2 /* PolylineClickMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47647DA995A6B25B95F3C3A /* PolylineClickMapPage.swift */; }; + 0581D8AB580DE46B07B57322 /* HeaderRecordingTileServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B93BB101525AB3833FF25C0 /* HeaderRecordingTileServer.swift */; }; 06EEC3A4595051957A53DE13 /* RasterLayerPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C878252AEEAFC99FEC69A95 /* RasterLayerPageViewModel.swift */; }; 081CEE5396A315A9EF3E7A2B /* RasterLayerMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A79993FD9DEFEE0B183916 /* RasterLayerMapPage.swift */; }; + 0AE7FB160024B8B485EB15EB /* LongdoInfoBubbleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E37BD0E38E109FB9A88C7FEF /* LongdoInfoBubbleUITests.swift */; }; + 0DB0C1C45CD99B44262FF919 /* FitBoundsMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7C4718260907A3770D50458 /* FitBoundsMapComponent.swift */; }; 0E0AB02ADFBFA812B33DBDEC /* VisibleRegionPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAF083CC1444EF2343A56F7 /* VisibleRegionPage.swift */; }; 0FAC625D001BF5EC93954F1E /* PolylineMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BE5BB5D5DB84A8B5E698BB4 /* PolylineMapPage.swift */; }; + 105AB28EF7DC4395C8F056A3 /* MapConductorGeoJSON in Frameworks */ = {isa = PBXBuildFile; productRef = A2893889C0B45F35A8DB0C4B /* MapConductorGeoJSON */; }; 10E5AF39816AAE3BD1C04F18 /* GroundImageMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 235E67A1859B3CA8F12D37E8 /* GroundImageMapComponent.swift */; }; 129EBE1EB46F88C4E4715E1A /* PostOfficeMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C36C321C43614D74792FE49 /* PostOfficeMapComponent.swift */; }; + 15BFFF5AEE90E30A1648BF9B /* MapConductorForTomTom in Frameworks */ = {isa = PBXBuildFile; productRef = 7D730C3AFB56BCACB70D7B43 /* MapConductorForTomTom */; }; 17309AC5A47C71C08F82311D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313B53E1BB15ED36A24D884A /* ContentView.swift */; }; + 17B5F2B2856E7710BC987153 /* UISettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D764AEA50006E42D46EF8E42 /* UISettingsUITests.swift */; }; 18183758DF7D84609BBC70E5 /* StoreMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B129F8127A4F68D7014EF037 /* StoreMapComponent.swift */; }; 1AB418776A864E3F7EA42470 /* GroundImageResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029F54738EC2590081CA44A8 /* GroundImageResources.swift */; }; 1ECF732832616FF083AC089D /* FlyToPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 496B5E1E6513458E647DF810 /* FlyToPageViewModel.swift */; }; - 21DEE995EF7CDCC705716720 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = CC49320174E28DBBB2DA3E3B /* ZIPFoundation */; }; + 21DEE995EF7CDCC705716720 /* MapConductorForLongdo in Frameworks */ = {isa = PBXBuildFile; productRef = 035F4F1283704F98E8E7C663 /* MapConductorForLongdo */; }; + 221F14F85019FD4F1742B53A /* PolylineGeodesicPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D663A887D0AFCDCAF6E4EB7 /* PolylineGeodesicPageViewModel.swift */; }; 239206A33F5255AFF2ED073D /* MapDesignMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536A6FD93067C70FAF6FC8CE /* MapDesignMapPage.swift */; }; 284B63D3105246B496337FDC /* SampleConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 673785C2388A204D53E08EF9 /* SampleConfig.swift */; }; 2C31107D1F3D76A5987BC55E /* FlyToMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E7322320F385235CECACA81 /* FlyToMapComponent.swift */; }; + 32CC3376ECDC8BE23CBE1B46 /* ExampleGeoJSONLayerLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCDDCC99B087D3B1329444A /* ExampleGeoJSONLayerLoader.swift */; }; 346966A6732A7E80DF7BD137 /* PolygonGeodesicPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A543622B3A557165F7C8E10 /* PolygonGeodesicPage.swift */; }; 3494CEED6AE66BCBD33AC145 /* HeatmapPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B454F900084472843036F8CF /* HeatmapPageViewModel.swift */; }; + 385A04B7F95CE23B5F6D9043 /* MapConductorForMapLibre in Frameworks */ = {isa = PBXBuildFile; productRef = DE9E4D481C91003D86901307 /* MapConductorForMapLibre */; }; 393D3040E362D5FAEC3A6DA9 /* HeatmapMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2601B9681BC7350C9B999E5 /* HeatmapMapPage.swift */; }; + 39BD7B6F805E581E3ECD665D /* ProviderSwitchUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C711748AE5022E33B5DCC9 /* ProviderSwitchUITests.swift */; }; 3DE14E8C2ED828F123AFEC78 /* HeatmapMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4169D6811740458CDDA8ED /* HeatmapMapComponent.swift */; }; + 439ED392E79B767B159AB7A3 /* TiltMapPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 619427C6B7849A8B98677C4A /* TiltMapPageViewModel.swift */; }; 455F6D8DBBAF522E01199F4A /* California.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05DCD88C1178628A7AD754C3 /* California.swift */; }; 46F5E8DF912419D9D2787CA9 /* PolylineMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E60581AEC1C00F85895B924E /* PolylineMapComponent.swift */; }; 4A74FE88789A45D86D8C60F6 /* PolygonMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5A5CFFEF92694444627765 /* PolygonMapComponent.swift */; }; + 4B0BA6BA90381FC6641DD555 /* HereHitTestUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFD29F27997EB2915FB99617 /* HereHitTestUITests.swift */; }; 4BDB1626B06E2B304B965E70 /* StoreMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB49FEC2EA6F72C7A90061E /* StoreMapPage.swift */; }; 4F6E927027C5A68A2545D076 /* StyledInfoBubblePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5828B2F881475BCBDAA22B88 /* StyledInfoBubblePage.swift */; }; 4F81EC1F484EB83629097CD8 /* PolylineClickPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 635B5B791B73E89988420538 /* PolylineClickPageViewModel.swift */; }; @@ -38,63 +50,104 @@ 54B5880433557F1FC0C478ED /* PolygonClickPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EF3B3111BFBB17B5B1C14F2 /* PolygonClickPageViewModel.swift */; }; 56AAABDA4A3F2F7B7D1BBC8B /* VisibleRegionMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47498C58CDEE0E9CF7342A52 /* VisibleRegionMapComponent.swift */; }; 57170F6394EBFE012560B267 /* DemoMapPageScaffold.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D4F852BF6BEE5EB887B62B /* DemoMapPageScaffold.swift */; }; + 58EDB1BF6B43FC714812ED94 /* SpiderfyUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EF2B7EE37AC5A382BFA399 /* SpiderfyUITests.swift */; }; + 5A53C030EE4F908F7FC106C8 /* CameraRestrictionPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55FF87F1B16C72C5140C208F /* CameraRestrictionPage.swift */; }; 5D389D96A80C37356179AEE2 /* DemoData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B3C8F4443CED133798AA355 /* DemoData.swift */; }; 5E917641E9ED4BE2C183CB8F /* MarkerBasicMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866556D6922D74C2445D5771 /* MarkerBasicMapComponent.swift */; }; + 5FB3C13BA06F1482B4661242 /* GeoJSONLayerMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6BD26E6526A17508DFDE48 /* GeoJSONLayerMapPage.swift */; }; 62B38FB816A90530C5E94533 /* Sidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB437D06DC85F2E05CC7F4B3 /* Sidebar.swift */; }; + 6318C0A7B64F8D69DE1EEE24 /* FitBoundsMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0BDC9303996CAFA67CCB72 /* FitBoundsMapPage.swift */; }; 631CDE728E8CCC6DEAAC005C /* CirclePageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FD3CF585E712FF3B248B0F /* CirclePageViewModel.swift */; }; - 69815A192F7C41980013BC71 /* MapConductorHeatmap in Frameworks */ = {isa = PBXBuildFile; productRef = 69815A182F7C41980013BC71 /* MapConductorHeatmap */; }; - 69815A1C2F7C41A70013BC71 /* MapConductorMarkerCluster in Frameworks */ = {isa = PBXBuildFile; productRef = 69815A1B2F7C41A70013BC71 /* MapConductorMarkerCluster */; }; - 69815A1F2F7C41C00013BC71 /* MapConductorForGoogleMaps in Frameworks */ = {isa = PBXBuildFile; productRef = 69815A1E2F7C41C00013BC71 /* MapConductorForGoogleMaps */; }; - 69815A222F7C41D00013BC71 /* MapConductorForMapbox in Frameworks */ = {isa = PBXBuildFile; productRef = 69815A212F7C41D00013BC71 /* MapConductorForMapbox */; }; - 69815A252F7C41E00013BC71 /* MapConductorForMapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 69815A242F7C41E00013BC71 /* MapConductorForMapKit */; }; - 69815A282F7C41EF0013BC71 /* MapConductorForMapLibre in Frameworks */ = {isa = PBXBuildFile; productRef = 69815A272F7C41EF0013BC71 /* MapConductorForMapLibre */; }; + 634EA220584E00AAFD8022F3 /* MapConductorForGoogleMaps in Frameworks */ = {isa = PBXBuildFile; productRef = 06C1DBF8CB687729DF1C807A /* MapConductorForGoogleMaps */; }; + 6BD87DE57C766D0BB384A240 /* CameraRestrictionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C6709BFAFDBBE0C3D2504B /* CameraRestrictionUITests.swift */; }; 6D2317AFDDDAEB77C961EC26 /* MapDesignMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C0AB60B489874EBCF704F5 /* MapDesignMapComponent.swift */; }; + 6F54D000AA0F05FEF5CE7E1A /* GoogleMaps in Frameworks */ = {isa = PBXBuildFile; productRef = F67792B83660D03AE335EDCD /* GoogleMaps */; }; 70FA687400503741ACC26B5E /* RichContentBubblePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF2D8AE048398EAB46BC1455 /* RichContentBubblePage.swift */; }; 729F6A4535B02CE619998235 /* StoreMapPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE52221880CD793F1BF0560 /* StoreMapPageViewModel.swift */; }; 72BDA654C664E0E9D9567BB1 /* GroundImagePageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E16C76A9F0BB857BA08951 /* GroundImagePageViewModel.swift */; }; - 754B2C5F6F912D23720FD7A9 /* GoogleMaps in Frameworks */ = {isa = PBXBuildFile; productRef = F67792B83660D03AE335EDCD /* GoogleMaps */; }; + 754B2C5F6F912D23720FD7A9 /* MapConductorForMapTiler in Frameworks */ = {isa = PBXBuildFile; productRef = DD849FDD6B93F3EDA511B6F1 /* MapConductorForMapTiler */; }; 768036852114CA499289CB4D /* MapDesignPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39DC859AB185EB39E357FBB2 /* MapDesignPageViewModel.swift */; }; 78099986D8558396904424B8 /* PostOfficeClusterMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9636CBE9C7019A27A04C147 /* PostOfficeClusterMapPage.swift */; }; + 7AE25EB485CF3E07C6855302 /* HereHitTestPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B1FF3E2CC528F12B3F4EAD /* HereHitTestPage.swift */; }; + 7B21D76122110C43BD9D414F /* RasterHeaderUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D788830F4469D4610C71A869 /* RasterHeaderUITests.swift */; }; 82F246CF9D7DABFECC0BAB49 /* MultipleBubblesPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D48D5B0D7E2125723C3A9DE /* MultipleBubblesPage.swift */; }; 8703219D862CFC787CC2B082 /* PolylinePageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C15792FC2A80B01CD49E08 /* PolylinePageViewModel.swift */; }; 876A63315A19B8E0CBBD8FB9 /* StoreInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596B67D7F433BEA427C799BF /* StoreInfoView.swift */; }; 879866CE6BA654FDBDFE1C3D /* CameraSyncTestPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0403006AA6C5EB150EABD2 /* CameraSyncTestPage.swift */; }; + 886D5CA070732881773AC4BD /* MapConductorCore in Frameworks */ = {isa = PBXBuildFile; productRef = 5F868FD6810605D51D8DE0E0 /* MapConductorCore */; }; + 88D5641422025010188F524B /* MapConductorForHERE in Frameworks */ = {isa = PBXBuildFile; productRef = C8D5A9C0CCED6456B3D81D24 /* MapConductorForHERE */; }; 8B51C0F020F2CFCAAF071D1C /* AnimationMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58F15C0FEEEB504570717B7 /* AnimationMapPage.swift */; }; + 8C557E443C67FAEFAE20FF01 /* StyleReloadPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAF468FB00E39057C7B9C96 /* StyleReloadPage.swift */; }; + 8DD9330782039E90CD911444 /* UISettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91F0A048B1B580AC5BE76075 /* UISettingsPage.swift */; }; 8F349E5133D9A348DB0F6E85 /* CircleMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C5796C83B450E126E53BD5F /* CircleMapPage.swift */; }; + 8F6DF2FC54B36BC96B6C8BE4 /* MapConductorHeatmap in Frameworks */ = {isa = PBXBuildFile; productRef = DF824C7DFDFC731E797795D6 /* MapConductorHeatmap */; }; + 8FB9E5BE76F30E432DCC8174 /* InfoBubbleCustomUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DE6402C500A3CC965F4FEF9 /* InfoBubbleCustomUITests.swift */; }; + 973E88576456DA1C7FF2B8AF /* FitBoundsPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09E3098A7041DD92F4CDA5D /* FitBoundsPageViewModel.swift */; }; + 9BE1985582B254F7E7F9E77F /* MarkerDragUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F98351A07D308D4D7F2A6E /* MarkerDragUITests.swift */; }; A06A7AA7386A13FD0F087D54 /* AnimationMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 247ED7633D70326148E9523A /* AnimationMapComponent.swift */; }; A902BC97B29AFA4BBBD8310A /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23BE35F0F841264AED454DE /* SidebarItem.swift */; }; A92A4D5BB420A460B5E6FED4 /* SampleMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8281BE72BC47056A7E6108BB /* SampleMapView.swift */; }; + A955322BEA38C109B9581248 /* StyleReloadUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F98B052B5A45377554CC9C2D /* StyleReloadUITests.swift */; }; AA4893EDEE9ED83024F91791 /* PostOfficeClusterPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2AB39A80E3A5C65B944467 /* PostOfficeClusterPageViewModel.swift */; }; + AB3A278E630050C0B69D29C9 /* MapConductorMarkerCluster in Frameworks */ = {isa = PBXBuildFile; productRef = FEB8A11AFC6869DC28064B3D /* MapConductorMarkerCluster */; }; + B010C23C9F28D88F616EACBC /* ExampleGeoJSONStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FE164C5622D5ABA920992F /* ExampleGeoJSONStyler.swift */; }; + B2B112AA7F88521C3FD10FDD /* HolePolygonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08101FE49CA383BD346A656C /* HolePolygonUITests.swift */; }; BD2731DF6E02759221CA5C94 /* CameraSyncTestViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C251EA117D0D739F43944D94 /* CameraSyncTestViewModel.swift */; }; BE5D7705F2D71DD9DE900C5D /* PostOfficeDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F129A8A5CDCF49717F09F1 /* PostOfficeDataLoader.swift */; }; C4CDD243D337D97B10CFAFBF /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B02E9F2ADE08AB6F6A9F0F3 /* NavigationViewModel.swift */; }; + CB49E4C305852E171446DA2E /* HeatmapCameraUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8730F46EA8AA766554BD56 /* HeatmapCameraUITests.swift */; }; + CBAF018856AD8D7727B0904C /* MapConductorForMapbox in Frameworks */ = {isa = PBXBuildFile; productRef = 07F84CB727B4D7BFA9E6BBA9 /* MapConductorForMapbox */; }; CC0E3528375293C416C891DB /* PostOfficePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA7A17FE65561B4737D7AC2 /* PostOfficePage.swift */; }; CFDB054F9C8EA4B05168DF2F /* TokyoPostOffices.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE316F0873F733D073DCFB /* TokyoPostOffices.swift */; }; + D1683E97E10E09AD57B0C7BE /* TiltMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB13F476A2A9A93D09A42D9E /* TiltMapPage.swift */; }; + D39229103CD9AC99AB00B6EA /* HolePolygonMapPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3753C91B977BC9754F803F69 /* HolePolygonMapPageViewModel.swift */; }; D5165A402B239CCB97FD2FE1 /* PolylineClickMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372828CAD4DECE4B66C4521D /* PolylineClickMapComponent.swift */; }; D538D79AC73CE6F51FC3C09B /* PostOfficeClusterMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71603F4704CD2F7AD581A0D3 /* PostOfficeClusterMapComponent.swift */; }; D9EE443DC85D3AE0F4EA3759 /* PostOfficeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF48ED67015660EDC4DC9ED /* PostOfficeViewModel.swift */; }; + DC8F656F22A74CAE5057E238 /* BasicGeoJSONMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E9ACEF15DA4EA6478E15919 /* BasicGeoJSONMapPage.swift */; }; + DF8E0172DD76E2A6300EBD6B /* MapConductorForArcGIS in Frameworks */ = {isa = PBXBuildFile; productRef = A9471523A3BF989FE54B2446 /* MapConductorForArcGIS */; }; + E08EBDEDB1603FF34CF6F427 /* RasterHeaderPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A9E514E0D709DBB2647B86 /* RasterHeaderPage.swift */; }; + E1E2A6D2D99982653D935CDC /* PolylineGeodesicPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C777804F8BD63B74CE6ED7A1 /* PolylineGeodesicPage.swift */; }; + E467A41D66C8BF026BD956B7 /* LargeMarkerSwitchUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539010E48B6C8FEF66E0F280 /* LargeMarkerSwitchUITests.swift */; }; E55D7E7E754923BB4899F1D0 /* PolygonClickMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5E4FF8CA6CC53779C785BE /* PolygonClickMapPage.swift */; }; E57233C3053759A16911AE99 /* MarkerBasicPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C941BEF1C5CDE12E6D636E /* MarkerBasicPage.swift */; }; E7096B512D8CCD1AE8F80778 /* MapConductorSampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2FE7D0C3C813CCED5F3853E /* MapConductorSampleApp.swift */; }; E7C4E5761F050CF81C011849 /* PostOfficeInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D14DA589632894B5B0FBC4 /* PostOfficeInfoView.swift */; }; EB99FD9F2B59CCDF59EFC878 /* AnimationPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C78BFE289DDEEABFFDB4D9 /* AnimationPageViewModel.swift */; }; + EC1A51ADD6A50B2BE33AE815 /* HolePolygonMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB49746B0EAA41AF2083493 /* HolePolygonMapPage.swift */; }; EE24664CA89BC54AB585AD7E /* PolygonMapPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E905A2A41F668FBF19BC9519 /* PolygonMapPageViewModel.swift */; }; F0B349FC52422B6A48908AA2 /* SimpleTextBubblePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16FCD4A32E5D0E85AB2E1024 /* SimpleTextBubblePage.swift */; }; F1984AA149912DDD52D4B8B9 /* RasterLayerMapComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 803C0755788E22BF686AB8DB /* RasterLayerMapComponent.swift */; }; + F520A5493A78D7D156153507 /* MapConductorForMapKit in Frameworks */ = {isa = PBXBuildFile; productRef = BCE99CF68028363B1329EC64 /* MapConductorForMapKit */; }; F8BD7D7E4AD94A01F07C838D /* DemoAppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0224D133C8AE0A8F51D465BD /* DemoAppScreen.swift */; }; F8D38831B9AFBC8150987736 /* GroundImageMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33936F78F0FA083376362231 /* GroundImageMapPage.swift */; }; F9CDC7F7EDE828E44973B26E /* PolygonGeodesicPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892A43B49256809B228489B8 /* PolygonGeodesicPageViewModel.swift */; }; FA0747BF013C9E65676F8838 /* VisibleRegionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC94ABA3629ED355A22E5540 /* VisibleRegionViewModel.swift */; }; FC61029EEAAF0FDBD4A5649F /* PolygonMapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDE7D6165092E3147E09F58 /* PolygonMapPage.swift */; }; + FE7DB5E571C7BD47A5729A21 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = CC49320174E28DBBB2DA3E3B /* ZIPFoundation */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + DB8AC8FE011D53CD1E93DE96 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 027B56AE645A6E8D32E682FE /* Project object */; + proxyType = 1; + remoteGlobalIDString = 59602D30B1131DB7A9B05787; + remoteInfo = MapConductorSampleApp; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 01A79993FD9DEFEE0B183916 /* RasterLayerMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RasterLayerMapPage.swift; sourceTree = ""; }; 0224D133C8AE0A8F51D465BD /* DemoAppScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoAppScreen.swift; sourceTree = ""; }; 029F54738EC2590081CA44A8 /* GroundImageResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroundImageResources.swift; sourceTree = ""; }; 05DCD88C1178628A7AD754C3 /* California.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = California.swift; sourceTree = ""; }; + 06F98351A07D308D4D7F2A6E /* MarkerDragUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkerDragUITests.swift; sourceTree = ""; }; 07E56B15B236BE92BD93461D /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Secrets.xcconfig; sourceTree = ""; }; + 08101FE49CA383BD346A656C /* HolePolygonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HolePolygonUITests.swift; sourceTree = ""; }; 0B2B7B69B3FA403C6F675D8F /* ios-for-maplibre */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-for-maplibre"; path = "../../ios-for-maplibre"; sourceTree = SOURCE_ROOT; }; + 0B93BB101525AB3833FF25C0 /* HeaderRecordingTileServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderRecordingTileServer.swift; sourceTree = ""; }; + 115EEADF4253AE3F62E349B7 /* ios-heatmap */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-heatmap"; path = "../../ios-heatmap"; sourceTree = SOURCE_ROOT; }; 16FCD4A32E5D0E85AB2E1024 /* SimpleTextBubblePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleTextBubblePage.swift; sourceTree = ""; }; 1B02E9F2ADE08AB6F6A9F0F3 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = ""; }; 1BE5BB5D5DB84A8B5E698BB4 /* PolylineMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolylineMapPage.swift; sourceTree = ""; }; @@ -103,20 +156,33 @@ 235E67A1859B3CA8F12D37E8 /* GroundImageMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroundImageMapComponent.swift; sourceTree = ""; }; 247ED7633D70326148E9523A /* AnimationMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationMapComponent.swift; sourceTree = ""; }; 2A543622B3A557165F7C8E10 /* PolygonGeodesicPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolygonGeodesicPage.swift; sourceTree = ""; }; + 2B9266EDD049DA45972FDCE9 /* ios-for-tomtom */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-for-tomtom"; path = "../../ios-for-tomtom"; sourceTree = SOURCE_ROOT; }; 2C36C321C43614D74792FE49 /* PostOfficeMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOfficeMapComponent.swift; sourceTree = ""; }; + 2D663A887D0AFCDCAF6E4EB7 /* PolylineGeodesicPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolylineGeodesicPageViewModel.swift; sourceTree = ""; }; + 2D8B401108F00801E7244C06 /* ios-geojson-layer */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-geojson-layer"; path = "../../ios-geojson-layer"; sourceTree = SOURCE_ROOT; }; + 2E9ACEF15DA4EA6478E15919 /* BasicGeoJSONMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicGeoJSONMapPage.swift; sourceTree = ""; }; 313B53E1BB15ED36A24D884A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 32B1FF3E2CC528F12B3F4EAD /* HereHitTestPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HereHitTestPage.swift; sourceTree = ""; }; 33936F78F0FA083376362231 /* GroundImageMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroundImageMapPage.swift; sourceTree = ""; }; 372828CAD4DECE4B66C4521D /* PolylineClickMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolylineClickMapComponent.swift; sourceTree = ""; }; + 3753C91B977BC9754F803F69 /* HolePolygonMapPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HolePolygonMapPageViewModel.swift; sourceTree = ""; }; 39DC859AB185EB39E357FBB2 /* MapDesignPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDesignPageViewModel.swift; sourceTree = ""; }; 3DE52221880CD793F1BF0560 /* StoreMapPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreMapPageViewModel.swift; sourceTree = ""; }; + 3FAF468FB00E39057C7B9C96 /* StyleReloadPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleReloadPage.swift; sourceTree = ""; }; 47498C58CDEE0E9CF7342A52 /* VisibleRegionMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleRegionMapComponent.swift; sourceTree = ""; }; + 47EF2B7EE37AC5A382BFA399 /* SpiderfyUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpiderfyUITests.swift; sourceTree = ""; }; 496B5E1E6513458E647DF810 /* FlyToPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlyToPageViewModel.swift; sourceTree = ""; }; 4B8345A4E4E2060505B6AFAF /* FlyToMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlyToMapPage.swift; sourceTree = ""; }; 4C4169D6811740458CDDA8ED /* HeatmapMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapMapComponent.swift; sourceTree = ""; }; + 4DE6402C500A3CC965F4FEF9 /* InfoBubbleCustomUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoBubbleCustomUITests.swift; sourceTree = ""; }; 51C699EF9F4BAE6967A14536 /* ios-for-mapkit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-for-mapkit"; path = "../../ios-for-mapkit"; sourceTree = SOURCE_ROOT; }; 536A6FD93067C70FAF6FC8CE /* MapDesignMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDesignMapPage.swift; sourceTree = ""; }; + 539010E48B6C8FEF66E0F280 /* LargeMarkerSwitchUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeMarkerSwitchUITests.swift; sourceTree = ""; }; + 55FF87F1B16C72C5140C208F /* CameraRestrictionPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraRestrictionPage.swift; sourceTree = ""; }; + 5675E69177526509D92883B2 /* ios-for-arcgis */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-for-arcgis"; path = "../../ios-for-arcgis"; sourceTree = SOURCE_ROOT; }; 5828B2F881475BCBDAA22B88 /* StyledInfoBubblePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyledInfoBubblePage.swift; sourceTree = ""; }; 596B67D7F433BEA427C799BF /* StoreInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreInfoView.swift; sourceTree = ""; }; + 619427C6B7849A8B98677C4A /* TiltMapPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TiltMapPageViewModel.swift; sourceTree = ""; }; 635B5B791B73E89988420538 /* PolylineClickPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolylineClickPageViewModel.swift; sourceTree = ""; }; 673785C2388A204D53E08EF9 /* SampleConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleConfig.swift; sourceTree = ""; }; 6EAF083CC1444EF2343A56F7 /* VisibleRegionPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleRegionPage.swift; sourceTree = ""; }; @@ -129,44 +195,68 @@ 892A43B49256809B228489B8 /* PolygonGeodesicPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolygonGeodesicPageViewModel.swift; sourceTree = ""; }; 8AF48ED67015660EDC4DC9ED /* PostOfficeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOfficeViewModel.swift; sourceTree = ""; }; 8B3C8F4443CED133798AA355 /* DemoData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoData.swift; sourceTree = ""; }; + 8BC61F8A5C2B9C5876A0F036 /* ios-for-here */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-for-here"; path = "../../ios-for-here"; sourceTree = SOURCE_ROOT; }; 8C5796C83B450E126E53BD5F /* CircleMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleMapPage.swift; sourceTree = ""; }; 8D48D5B0D7E2125723C3A9DE /* MultipleBubblesPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleBubblesPage.swift; sourceTree = ""; }; 8EF3B3111BFBB17B5B1C14F2 /* PolygonClickPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolygonClickPageViewModel.swift; sourceTree = ""; }; 8FDE7D6165092E3147E09F58 /* PolygonMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolygonMapPage.swift; sourceTree = ""; }; + 91F0A048B1B580AC5BE76075 /* UISettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISettingsPage.swift; sourceTree = ""; }; + 9C0BDC9303996CAFA67CCB72 /* FitBoundsMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FitBoundsMapPage.swift; sourceTree = ""; }; A58F15C0FEEEB504570717B7 /* AnimationMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationMapPage.swift; sourceTree = ""; }; A7C78BFE289DDEEABFFDB4D9 /* AnimationPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationPageViewModel.swift; sourceTree = ""; }; + AA8730F46EA8AA766554BD56 /* HeatmapCameraUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapCameraUITests.swift; sourceTree = ""; }; AD9E7DD79ABF1B72A4B13050 /* ios-sdk-core */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-sdk-core"; path = "../../ios-sdk-core"; sourceTree = SOURCE_ROOT; }; AF2D8AE048398EAB46BC1455 /* RichContentBubblePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichContentBubblePage.swift; sourceTree = ""; }; B129F8127A4F68D7014EF037 /* StoreMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreMapComponent.swift; sourceTree = ""; }; B2FE7D0C3C813CCED5F3853E /* MapConductorSampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapConductorSampleApp.swift; sourceTree = ""; }; B3413C5C263D93E79F5C6F79 /* CircleMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleMapComponent.swift; sourceTree = ""; }; B454F900084472843036F8CF /* HeatmapPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapPageViewModel.swift; sourceTree = ""; }; + B5FE164C5622D5ABA920992F /* ExampleGeoJSONStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleGeoJSONStyler.swift; sourceTree = ""; }; BAB49FEC2EA6F72C7A90061E /* StoreMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreMapPage.swift; sourceTree = ""; }; BB437D06DC85F2E05CC7F4B3 /* Sidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sidebar.swift; sourceTree = ""; }; BC94ABA3629ED355A22E5540 /* VisibleRegionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleRegionViewModel.swift; sourceTree = ""; }; BF5A5CFFEF92694444627765 /* PolygonMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolygonMapComponent.swift; sourceTree = ""; }; + C19B0528806C7E85A3AD6415 /* ios-for-longdo */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-for-longdo"; path = "../../ios-for-longdo"; sourceTree = SOURCE_ROOT; }; C1D4F852BF6BEE5EB887B62B /* DemoMapPageScaffold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoMapPageScaffold.swift; sourceTree = ""; }; C251EA117D0D739F43944D94 /* CameraSyncTestViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSyncTestViewModel.swift; sourceTree = ""; }; C2C941BEF1C5CDE12E6D636E /* MarkerBasicPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkerBasicPage.swift; sourceTree = ""; }; + C4C6709BFAFDBBE0C3D2504B /* CameraRestrictionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraRestrictionUITests.swift; sourceTree = ""; }; C5F51605FCA8E08A095FC699 /* ios-for-mapbox */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-for-mapbox"; path = "../../ios-for-mapbox"; sourceTree = SOURCE_ROOT; }; - C892B05755C2A7666D2F8C00 /* MapConductorSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MapConductorSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C777804F8BD63B74CE6ED7A1 /* PolylineGeodesicPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolylineGeodesicPage.swift; sourceTree = ""; }; + C892B05755C2A7666D2F8C00 /* MapConductorSampleApp.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = MapConductorSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; CA2AB39A80E3A5C65B944467 /* PostOfficeClusterPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOfficeClusterPageViewModel.swift; sourceTree = ""; }; + CA87900CF58E0DB99C6A3D44 /* MapConductorSampleAppUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = MapConductorSampleAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CB0403006AA6C5EB150EABD2 /* CameraSyncTestPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSyncTestPage.swift; sourceTree = ""; }; + CBB49746B0EAA41AF2083493 /* HolePolygonMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HolePolygonMapPage.swift; sourceTree = ""; }; CCB28D67275FF6999E2D5BA3 /* ios-for-googlemaps */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-for-googlemaps"; path = "../../ios-for-googlemaps"; sourceTree = SOURCE_ROOT; }; + CFD29F27997EB2915FB99617 /* HereHitTestUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HereHitTestUITests.swift; sourceTree = ""; }; D0FD3CF585E712FF3B248B0F /* CirclePageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CirclePageViewModel.swift; sourceTree = ""; }; D1D14DA589632894B5B0FBC4 /* PostOfficeInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOfficeInfoView.swift; sourceTree = ""; }; + D729856F6C332EFE88125934 /* ios-for-maptiler */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-for-maptiler"; path = "../../ios-for-maptiler"; sourceTree = SOURCE_ROOT; }; + D764AEA50006E42D46EF8E42 /* UISettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISettingsUITests.swift; sourceTree = ""; }; + D788830F4469D4610C71A869 /* RasterHeaderUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RasterHeaderUITests.swift; sourceTree = ""; }; + D7C711748AE5022E33B5DCC9 /* ProviderSwitchUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderSwitchUITests.swift; sourceTree = ""; }; + DDCDDCC99B087D3B1329444A /* ExampleGeoJSONLayerLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleGeoJSONLayerLoader.swift; sourceTree = ""; }; DDCE316F0873F733D073DCFB /* TokyoPostOffices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokyoPostOffices.swift; sourceTree = ""; }; DFA7A17FE65561B4737D7AC2 /* PostOfficePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOfficePage.swift; sourceTree = ""; }; + E09E3098A7041DD92F4CDA5D /* FitBoundsPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FitBoundsPageViewModel.swift; sourceTree = ""; }; E2601B9681BC7350C9B999E5 /* HeatmapMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeatmapMapPage.swift; sourceTree = ""; }; E2F129A8A5CDCF49717F09F1 /* PostOfficeDataLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOfficeDataLoader.swift; sourceTree = ""; }; + E37BD0E38E109FB9A88C7FEF /* LongdoInfoBubbleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongdoInfoBubbleUITests.swift; sourceTree = ""; }; E47647DA995A6B25B95F3C3A /* PolylineClickMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolylineClickMapPage.swift; sourceTree = ""; }; E4C15792FC2A80B01CD49E08 /* PolylinePageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolylinePageViewModel.swift; sourceTree = ""; }; E60581AEC1C00F85895B924E /* PolylineMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolylineMapComponent.swift; sourceTree = ""; }; + E7C4718260907A3770D50458 /* FitBoundsMapComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FitBoundsMapComponent.swift; sourceTree = ""; }; E905A2A41F668FBF19BC9519 /* PolygonMapPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolygonMapPageViewModel.swift; sourceTree = ""; }; + EB13F476A2A9A93D09A42D9E /* TiltMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TiltMapPage.swift; sourceTree = ""; }; ED5E4FF8CA6CC53779C785BE /* PolygonClickMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolygonClickMapPage.swift; sourceTree = ""; }; F23BE35F0F841264AED454DE /* SidebarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItem.swift; sourceTree = ""; }; F6E16C76A9F0BB857BA08951 /* GroundImagePageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroundImagePageViewModel.swift; sourceTree = ""; }; + F7A9E514E0D709DBB2647B86 /* RasterHeaderPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RasterHeaderPage.swift; sourceTree = ""; }; F9636CBE9C7019A27A04C147 /* PostOfficeClusterMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostOfficeClusterMapPage.swift; sourceTree = ""; }; + F98B052B5A45377554CC9C2D /* StyleReloadUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleReloadUITests.swift; sourceTree = ""; }; + FD6BD26E6526A17508DFDE48 /* GeoJSONLayerMapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJSONLayerMapPage.swift; sourceTree = ""; }; + FE117E1B8A843A840C03E8C0 /* ios-marker-cluster */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "ios-marker-cluster"; path = "../../ios-marker-cluster"; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -174,14 +264,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 69815A1C2F7C41A70013BC71 /* MapConductorMarkerCluster in Frameworks */, - 69815A282F7C41EF0013BC71 /* MapConductorForMapLibre in Frameworks */, - 69815A1F2F7C41C00013BC71 /* MapConductorForGoogleMaps in Frameworks */, - 69815A192F7C41980013BC71 /* MapConductorHeatmap in Frameworks */, - 69815A252F7C41E00013BC71 /* MapConductorForMapKit in Frameworks */, - 754B2C5F6F912D23720FD7A9 /* GoogleMaps in Frameworks */, - 69815A222F7C41D00013BC71 /* MapConductorForMapbox in Frameworks */, - 21DEE995EF7CDCC705716720 /* ZIPFoundation in Frameworks */, + 886D5CA070732881773AC4BD /* MapConductorCore in Frameworks */, + 634EA220584E00AAFD8022F3 /* MapConductorForGoogleMaps in Frameworks */, + 385A04B7F95CE23B5F6D9043 /* MapConductorForMapLibre in Frameworks */, + DF8E0172DD76E2A6300EBD6B /* MapConductorForArcGIS in Frameworks */, + F520A5493A78D7D156153507 /* MapConductorForMapKit in Frameworks */, + CBAF018856AD8D7727B0904C /* MapConductorForMapbox in Frameworks */, + 15BFFF5AEE90E30A1648BF9B /* MapConductorForTomTom in Frameworks */, + 754B2C5F6F912D23720FD7A9 /* MapConductorForMapTiler in Frameworks */, + 21DEE995EF7CDCC705716720 /* MapConductorForLongdo in Frameworks */, + 8F6DF2FC54B36BC96B6C8BE4 /* MapConductorHeatmap in Frameworks */, + 105AB28EF7DC4395C8F056A3 /* MapConductorGeoJSON in Frameworks */, + 88D5641422025010188F524B /* MapConductorForHERE in Frameworks */, + AB3A278E630050C0B69D29C9 /* MapConductorMarkerCluster in Frameworks */, + 6F54D000AA0F05FEF5CE7E1A /* GoogleMaps in Frameworks */, + FE7DB5E571C7BD47A5729A21 /* ZIPFoundation in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -238,6 +335,15 @@ path = animation; sourceTree = ""; }; + 338D7821AB4ADF4020C1B741 /* tilt */ = { + isa = PBXGroup; + children = ( + EB13F476A2A9A93D09A42D9E /* TiltMapPage.swift */, + 619427C6B7849A8B98677C4A /* TiltMapPageViewModel.swift */, + ); + path = tilt; + sourceTree = ""; + }; 358A74F0CE83FABD33E281FD /* Sources */ = { isa = PBXGroup; children = ( @@ -247,17 +353,27 @@ 673785C2388A204D53E08EF9 /* SampleConfig.swift */, 47480A7A5E61AD300F747FCF /* navigation */, C3BE3CC245D9DEEF5DF130CF /* pages */, + F4A9A3764B49F69E582A7BDA /* support */, F2D3AE87E970F5ADAC7EC53B /* ui */, ); path = Sources; sourceTree = ""; }; + 39F582A0817A67535939DBAF /* hittest */ = { + isa = PBXGroup; + children = ( + 32B1FF3E2CC528F12B3F4EAD /* HereHitTestPage.swift */, + ); + path = hittest; + sourceTree = ""; + }; 3D1DA908927B688810F6A817 /* polygon */ = { isa = PBXGroup; children = ( 723C6C30676589FE26A0FE93 /* basic */, 09413A4511380ED97D2F429F /* click */, F081C5FE9538A04A5EE86446 /* geodesic */, + 7669AD72FD7900D4331DA624 /* hole */, ); path = polygon; sourceTree = ""; @@ -290,6 +406,14 @@ path = icons; sourceTree = ""; }; + 58FD0D61DCB22932BFFE4D3B /* basic */ = { + isa = PBXGroup; + children = ( + 2E9ACEF15DA4EA6478E15919 /* BasicGeoJSONMapPage.swift */, + ); + path = basic; + sourceTree = ""; + }; 5B50BCB1A2C849055F6397AC /* basic */ = { isa = PBXGroup; children = ( @@ -300,12 +424,23 @@ path = basic; sourceTree = ""; }; + 5E273E361DC7DA0E49966342 /* fitbounds */ = { + isa = PBXGroup; + children = ( + E7C4718260907A3770D50458 /* FitBoundsMapComponent.swift */, + 9C0BDC9303996CAFA67CCB72 /* FitBoundsMapPage.swift */, + E09E3098A7041DD92F4CDA5D /* FitBoundsPageViewModel.swift */, + ); + path = fitbounds; + sourceTree = ""; + }; 6C190CD24E1B555B5E63A7D8 = { isa = PBXGroup; children = ( 03FE199E9B83358035F7AE46 /* Config */, BF6BE4052C3F7CC23A00E56C /* Packages */, 358A74F0CE83FABD33E281FD /* Sources */, + E004F0A022CC2E77F66B45A1 /* UITests */, 822B1C2F592366C166DFD730 /* Products */, ); sourceTree = ""; @@ -320,6 +455,23 @@ path = basic; sourceTree = ""; }; + 74F41D93F66B1BF1D8A5C3F0 /* camerarestriction */ = { + isa = PBXGroup; + children = ( + 55FF87F1B16C72C5140C208F /* CameraRestrictionPage.swift */, + ); + path = camerarestriction; + sourceTree = ""; + }; + 7669AD72FD7900D4331DA624 /* hole */ = { + isa = PBXGroup; + children = ( + CBB49746B0EAA41AF2083493 /* HolePolygonMapPage.swift */, + 3753C91B977BC9754F803F69 /* HolePolygonMapPageViewModel.swift */, + ); + path = hole; + sourceTree = ""; + }; 80CD2CD0E2CB85EFB411F92D /* postofficecluster */ = { isa = PBXGroup; children = ( @@ -335,6 +487,7 @@ isa = PBXGroup; children = ( C892B05755C2A7666D2F8C00 /* MapConductorSampleApp.app */, + CA87900CF58E0DB99C6A3D44 /* MapConductorSampleAppUITests.xctest */, ); name = Products; sourceTree = ""; @@ -349,6 +502,25 @@ path = click; sourceTree = ""; }; + 8F6A968E9375C1BA59FC78E9 /* layer */ = { + isa = PBXGroup; + children = ( + DDCDDCC99B087D3B1329444A /* ExampleGeoJSONLayerLoader.swift */, + B5FE164C5622D5ABA920992F /* ExampleGeoJSONStyler.swift */, + FD6BD26E6526A17508DFDE48 /* GeoJSONLayerMapPage.swift */, + ); + path = layer; + sourceTree = ""; + }; + 91AB1698A5AEF08E05FCA0BD /* geojson */ = { + isa = PBXGroup; + children = ( + 58FD0D61DCB22932BFFE4D3B /* basic */, + 8F6A968E9375C1BA59FC78E9 /* layer */, + ); + path = geojson; + sourceTree = ""; + }; 9DECACA325A80E44CB913DE4 /* heatmap */ = { isa = PBXGroup; children = ( @@ -370,6 +542,23 @@ path = circle; sourceTree = ""; }; + A8AE8676E0B6F0E5AF89308F /* geodesic */ = { + isa = PBXGroup; + children = ( + C777804F8BD63B74CE6ED7A1 /* PolylineGeodesicPage.swift */, + 2D663A887D0AFCDCAF6E4EB7 /* PolylineGeodesicPageViewModel.swift */, + ); + path = geodesic; + sourceTree = ""; + }; + A999DC77836849E66AC09E1B /* uisettings */ = { + isa = PBXGroup; + children = ( + 91F0A048B1B580AC5BE76075 /* UISettingsPage.swift */, + ); + path = uisettings; + sourceTree = ""; + }; B7B61FE3AF487F2B905B8C1A /* infobubble */ = { isa = PBXGroup; children = ( @@ -384,10 +573,18 @@ BF6BE4052C3F7CC23A00E56C /* Packages */ = { isa = PBXGroup; children = ( + 5675E69177526509D92883B2 /* ios-for-arcgis */, CCB28D67275FF6999E2D5BA3 /* ios-for-googlemaps */, + 8BC61F8A5C2B9C5876A0F036 /* ios-for-here */, + C19B0528806C7E85A3AD6415 /* ios-for-longdo */, C5F51605FCA8E08A095FC699 /* ios-for-mapbox */, 51C699EF9F4BAE6967A14536 /* ios-for-mapkit */, 0B2B7B69B3FA403C6F675D8F /* ios-for-maplibre */, + D729856F6C332EFE88125934 /* ios-for-maptiler */, + 2B9266EDD049DA45972FDCE9 /* ios-for-tomtom */, + 2D8B401108F00801E7244C06 /* ios-geojson-layer */, + 115EEADF4253AE3F62E349B7 /* ios-heatmap */, + FE117E1B8A843A840C03E8C0 /* ios-marker-cluster */, AD9E7DD79ABF1B72A4B13050 /* ios-sdk-core */, ); name = Packages; @@ -409,8 +606,10 @@ children = ( D5A7CD7831DA05B19EB584B9 /* camerasync */, A6A9A4C2D683D7E3F4E0B9C5 /* circle */, + 91AB1698A5AEF08E05FCA0BD /* geojson */, DD699D918DCB93A703C98F2E /* groundimage */, 9DECACA325A80E44CB913DE4 /* heatmap */, + 39F582A0817A67535939DBAF /* hittest */, B7B61FE3AF487F2B905B8C1A /* infobubble */, D58105C4BF3B711107B7106B /* map */, C157E16E11B5346736CC8A4B /* marker */, @@ -427,8 +626,13 @@ children = ( 8281BE72BC47056A7E6108BB /* SampleMapView.swift */, 05B116B454B7D9F625AF0B62 /* basic */, + 74F41D93F66B1BF1D8A5C3F0 /* camerarestriction */, F4919286D669DCCC4C2D7DCF /* design */, + 5E273E361DC7DA0E49966342 /* fitbounds */, 248DAB3F99A5A003037AC88C /* flyto */, + D6FD7844DE1A8E26AB43E6B5 /* stylereload */, + 338D7821AB4ADF4020C1B741 /* tilt */, + A999DC77836849E66AC09E1B /* uisettings */, FE43EDEDD792C393BC1D2319 /* visibleregion */, ); path = map; @@ -443,11 +647,20 @@ path = camerasync; sourceTree = ""; }; + D6FD7844DE1A8E26AB43E6B5 /* stylereload */ = { + isa = PBXGroup; + children = ( + 3FAF468FB00E39057C7B9C96 /* StyleReloadPage.swift */, + ); + path = stylereload; + sourceTree = ""; + }; D908CC94D2BBE202CE428CC1 /* polyline */ = { isa = PBXGroup; children = ( 5B50BCB1A2C849055F6397AC /* basic */, 867FE232A0C126FD4BCF4B25 /* click */, + A8AE8676E0B6F0E5AF89308F /* geodesic */, ); path = polyline; sourceTree = ""; @@ -471,6 +684,26 @@ path = groundimage; sourceTree = ""; }; + E004F0A022CC2E77F66B45A1 /* UITests */ = { + isa = PBXGroup; + children = ( + C4C6709BFAFDBBE0C3D2504B /* CameraRestrictionUITests.swift */, + AA8730F46EA8AA766554BD56 /* HeatmapCameraUITests.swift */, + CFD29F27997EB2915FB99617 /* HereHitTestUITests.swift */, + 08101FE49CA383BD346A656C /* HolePolygonUITests.swift */, + 4DE6402C500A3CC965F4FEF9 /* InfoBubbleCustomUITests.swift */, + 539010E48B6C8FEF66E0F280 /* LargeMarkerSwitchUITests.swift */, + E37BD0E38E109FB9A88C7FEF /* LongdoInfoBubbleUITests.swift */, + 06F98351A07D308D4D7F2A6E /* MarkerDragUITests.swift */, + D7C711748AE5022E33B5DCC9 /* ProviderSwitchUITests.swift */, + D788830F4469D4610C71A869 /* RasterHeaderUITests.swift */, + 47EF2B7EE37AC5A382BFA399 /* SpiderfyUITests.swift */, + F98B052B5A45377554CC9C2D /* StyleReloadUITests.swift */, + D764AEA50006E42D46EF8E42 /* UISettingsUITests.swift */, + ); + path = UITests; + sourceTree = ""; + }; EAC80F2FE30BA27175616408 /* sidebar */ = { isa = PBXGroup; children = ( @@ -483,6 +716,7 @@ ED4CE6C39BE8B20AD43B8184 /* raster */ = { isa = PBXGroup; children = ( + F7A9E514E0D709DBB2647B86 /* RasterHeaderPage.swift */, 803C0755788E22BF686AB8DB /* RasterLayerMapComponent.swift */, 01A79993FD9DEFEE0B183916 /* RasterLayerMapPage.swift */, 7C878252AEEAFC99FEC69A95 /* RasterLayerPageViewModel.swift */, @@ -518,6 +752,14 @@ path = design; sourceTree = ""; }; + F4A9A3764B49F69E582A7BDA /* support */ = { + isa = PBXGroup; + children = ( + 0B93BB101525AB3833FF25C0 /* HeaderRecordingTileServer.swift */, + ); + path = support; + sourceTree = ""; + }; FE43EDEDD792C393BC1D2319 /* visibleregion */ = { isa = PBXGroup; children = ( @@ -545,19 +787,44 @@ ); name = MapConductorSampleApp; packageProductDependencies = ( + 5F868FD6810605D51D8DE0E0 /* MapConductorCore */, + 06C1DBF8CB687729DF1C807A /* MapConductorForGoogleMaps */, + DE9E4D481C91003D86901307 /* MapConductorForMapLibre */, + A9471523A3BF989FE54B2446 /* MapConductorForArcGIS */, + BCE99CF68028363B1329EC64 /* MapConductorForMapKit */, + 07F84CB727B4D7BFA9E6BBA9 /* MapConductorForMapbox */, + 7D730C3AFB56BCACB70D7B43 /* MapConductorForTomTom */, + DD849FDD6B93F3EDA511B6F1 /* MapConductorForMapTiler */, + 035F4F1283704F98E8E7C663 /* MapConductorForLongdo */, + DF824C7DFDFC731E797795D6 /* MapConductorHeatmap */, + A2893889C0B45F35A8DB0C4B /* MapConductorGeoJSON */, + C8D5A9C0CCED6456B3D81D24 /* MapConductorForHERE */, + FEB8A11AFC6869DC28064B3D /* MapConductorMarkerCluster */, F67792B83660D03AE335EDCD /* GoogleMaps */, CC49320174E28DBBB2DA3E3B /* ZIPFoundation */, - 69815A182F7C41980013BC71 /* MapConductorHeatmap */, - 69815A1B2F7C41A70013BC71 /* MapConductorMarkerCluster */, - 69815A1E2F7C41C00013BC71 /* MapConductorForGoogleMaps */, - 69815A212F7C41D00013BC71 /* MapConductorForMapbox */, - 69815A242F7C41E00013BC71 /* MapConductorForMapKit */, - 69815A272F7C41EF0013BC71 /* MapConductorForMapLibre */, ); productName = MapConductorSampleApp; productReference = C892B05755C2A7666D2F8C00 /* MapConductorSampleApp.app */; productType = "com.apple.product-type.application"; }; + E5CE242915B025680347A20D /* MapConductorSampleAppUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = EF2F9075BD1D92D1704EE3A5 /* Build configuration list for PBXNativeTarget "MapConductorSampleAppUITests" */; + buildPhases = ( + 5D94EFEB29E6E27406CA9711 /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + 72745BDC03B8123B7F2E5B3E /* PBXTargetDependency */, + ); + name = MapConductorSampleAppUITests; + packageProductDependencies = ( + ); + productName = MapConductorSampleAppUITests; + productReference = CA87900CF58E0DB99C6A3D44 /* MapConductorSampleAppUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -568,12 +835,17 @@ LastUpgradeCheck = 1430; TargetAttributes = { 59602D30B1131DB7A9B05787 = { + DevelopmentTeam = 4DUVX7999A; ProvisioningStyle = Automatic; }; + E5CE242915B025680347A20D = { + DevelopmentTeam = 4DUVX7999A; + ProvisioningStyle = Automatic; + TestTargetID = 59602D30B1131DB7A9B05787; + }; }; }; buildConfigurationList = DBCD1BCF95F610ED2597A52B /* Build configuration list for PBXProject "MapConductorSampleApp" */; - compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -585,18 +857,27 @@ packageReferences = ( EA9904C5DC8D915C46A8309E /* XCRemoteSwiftPackageReference "ZIPFoundation" */, 27DBF6E8902DBA1D9B9323F0 /* XCRemoteSwiftPackageReference "ios-maps-sdk" */, - 694460312F7C3E8D00F7B253 /* XCLocalSwiftPackageReference "../../ios-sdk-core" */, - 69815A172F7C41980013BC71 /* XCLocalSwiftPackageReference "../../ios-heatmap" */, - 69815A1A2F7C41A70013BC71 /* XCLocalSwiftPackageReference "../../ios-marker-cluster" */, - 69815A1D2F7C41C00013BC71 /* XCLocalSwiftPackageReference "../../ios-for-googlemaps" */, - 69815A202F7C41D00013BC71 /* XCLocalSwiftPackageReference "../../ios-for-mapbox" */, - 69815A232F7C41E00013BC71 /* XCLocalSwiftPackageReference "../../ios-for-mapkit" */, - 69815A262F7C41EF0013BC71 /* XCLocalSwiftPackageReference "../../ios-for-maplibre" */, + AD164C64C62ED307B56C28E4 /* XCLocalSwiftPackageReference "../../ios-for-arcgis" */, + 82FF966BE2579177D409F1A3 /* XCLocalSwiftPackageReference "../../ios-for-googlemaps" */, + FEDBBDD983CCFED337925652 /* XCLocalSwiftPackageReference "../../ios-for-here" */, + 7C7281FBA1BC87C10DADAAAE /* XCLocalSwiftPackageReference "../../ios-for-longdo" */, + EFCDB199203BBDE61A1424B1 /* XCLocalSwiftPackageReference "../../ios-for-mapbox" */, + A0533065222B487AB4111AB9 /* XCLocalSwiftPackageReference "../../ios-for-mapkit" */, + 3BAA3B19709CE01C24CAB943 /* XCLocalSwiftPackageReference "../../ios-for-maplibre" */, + BF0F2FCA7969598D48B37AF1 /* XCLocalSwiftPackageReference "../../ios-for-maptiler" */, + BD32CF317AE66D8094450F62 /* XCLocalSwiftPackageReference "../../ios-for-tomtom" */, + 5182F458F2DB02C7AD92C609 /* XCLocalSwiftPackageReference "../../ios-geojson-layer" */, + 942816B2270F970F8C62B71B /* XCLocalSwiftPackageReference "../../ios-heatmap" */, + F49D7F82E184630C78F2E126 /* XCLocalSwiftPackageReference "../../ios-marker-cluster" */, + 9E8A0F65001B9CBC60F693CA /* XCLocalSwiftPackageReference "../../ios-sdk-core" */, ); + preferredProjectObjectVersion = 77; + productRefGroup = 822B1C2F592366C166DFD730 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 59602D30B1131DB7A9B05787 /* MapConductorSampleApp */, + E5CE242915B025680347A20D /* MapConductorSampleAppUITests */, ); }; /* End PBXProject section */ @@ -630,7 +911,9 @@ A06A7AA7386A13FD0F087D54 /* AnimationMapComponent.swift in Sources */, 8B51C0F020F2CFCAAF071D1C /* AnimationMapPage.swift in Sources */, EB99FD9F2B59CCDF59EFC878 /* AnimationPageViewModel.swift in Sources */, + DC8F656F22A74CAE5057E238 /* BasicGeoJSONMapPage.swift in Sources */, 455F6D8DBBAF522E01199F4A /* California.swift in Sources */, + 5A53C030EE4F908F7FC106C8 /* CameraRestrictionPage.swift in Sources */, 879866CE6BA654FDBDFE1C3D /* CameraSyncTestPage.swift in Sources */, BD2731DF6E02759221CA5C94 /* CameraSyncTestViewModel.swift in Sources */, 4FAA3CD1B1C81E78D5EFD48D /* CircleMapComponent.swift in Sources */, @@ -640,16 +923,26 @@ F8BD7D7E4AD94A01F07C838D /* DemoAppScreen.swift in Sources */, 5D389D96A80C37356179AEE2 /* DemoData.swift in Sources */, 57170F6394EBFE012560B267 /* DemoMapPageScaffold.swift in Sources */, + 32CC3376ECDC8BE23CBE1B46 /* ExampleGeoJSONLayerLoader.swift in Sources */, + B010C23C9F28D88F616EACBC /* ExampleGeoJSONStyler.swift in Sources */, + 0DB0C1C45CD99B44262FF919 /* FitBoundsMapComponent.swift in Sources */, + 6318C0A7B64F8D69DE1EEE24 /* FitBoundsMapPage.swift in Sources */, + 973E88576456DA1C7FF2B8AF /* FitBoundsPageViewModel.swift in Sources */, 2C31107D1F3D76A5987BC55E /* FlyToMapComponent.swift in Sources */, 0217B3005DD020C159AFEC21 /* FlyToMapPage.swift in Sources */, 1ECF732832616FF083AC089D /* FlyToPageViewModel.swift in Sources */, + 5FB3C13BA06F1482B4661242 /* GeoJSONLayerMapPage.swift in Sources */, 10E5AF39816AAE3BD1C04F18 /* GroundImageMapComponent.swift in Sources */, F8D38831B9AFBC8150987736 /* GroundImageMapPage.swift in Sources */, 72BDA654C664E0E9D9567BB1 /* GroundImagePageViewModel.swift in Sources */, 1AB418776A864E3F7EA42470 /* GroundImageResources.swift in Sources */, + 0581D8AB580DE46B07B57322 /* HeaderRecordingTileServer.swift in Sources */, 3DE14E8C2ED828F123AFEC78 /* HeatmapMapComponent.swift in Sources */, 393D3040E362D5FAEC3A6DA9 /* HeatmapMapPage.swift in Sources */, 3494CEED6AE66BCBD33AC145 /* HeatmapPageViewModel.swift in Sources */, + 7AE25EB485CF3E07C6855302 /* HereHitTestPage.swift in Sources */, + EC1A51ADD6A50B2BE33AE815 /* HolePolygonMapPage.swift in Sources */, + D39229103CD9AC99AB00B6EA /* HolePolygonMapPageViewModel.swift in Sources */, E7096B512D8CCD1AE8F80778 /* MapConductorSampleApp.swift in Sources */, 6D2317AFDDDAEB77C961EC26 /* MapDesignMapComponent.swift in Sources */, 239206A33F5255AFF2ED073D /* MapDesignMapPage.swift in Sources */, @@ -668,6 +961,8 @@ D5165A402B239CCB97FD2FE1 /* PolylineClickMapComponent.swift in Sources */, 029C92BC38D67AFD3EB2F8D2 /* PolylineClickMapPage.swift in Sources */, 4F81EC1F484EB83629097CD8 /* PolylineClickPageViewModel.swift in Sources */, + E1E2A6D2D99982653D935CDC /* PolylineGeodesicPage.swift in Sources */, + 221F14F85019FD4F1742B53A /* PolylineGeodesicPageViewModel.swift in Sources */, 46F5E8DF912419D9D2787CA9 /* PolylineMapComponent.swift in Sources */, 0FAC625D001BF5EC93954F1E /* PolylineMapPage.swift in Sources */, 8703219D862CFC787CC2B082 /* PolylinePageViewModel.swift in Sources */, @@ -679,6 +974,7 @@ 129EBE1EB46F88C4E4715E1A /* PostOfficeMapComponent.swift in Sources */, CC0E3528375293C416C891DB /* PostOfficePage.swift in Sources */, D9EE443DC85D3AE0F4EA3759 /* PostOfficeViewModel.swift in Sources */, + E08EBDEDB1603FF34CF6F427 /* RasterHeaderPage.swift in Sources */, F1984AA149912DDD52D4B8B9 /* RasterLayerMapComponent.swift in Sources */, 081CEE5396A315A9EF3E7A2B /* RasterLayerMapPage.swift in Sources */, 06EEC3A4595051957A53DE13 /* RasterLayerPageViewModel.swift in Sources */, @@ -693,16 +989,48 @@ 18183758DF7D84609BBC70E5 /* StoreMapComponent.swift in Sources */, 4BDB1626B06E2B304B965E70 /* StoreMapPage.swift in Sources */, 729F6A4535B02CE619998235 /* StoreMapPageViewModel.swift in Sources */, + 8C557E443C67FAEFAE20FF01 /* StyleReloadPage.swift in Sources */, 4F6E927027C5A68A2545D076 /* StyledInfoBubblePage.swift in Sources */, + D1683E97E10E09AD57B0C7BE /* TiltMapPage.swift in Sources */, + 439ED392E79B767B159AB7A3 /* TiltMapPageViewModel.swift in Sources */, CFDB054F9C8EA4B05168DF2F /* TokyoPostOffices.swift in Sources */, + 8DD9330782039E90CD911444 /* UISettingsPage.swift in Sources */, 56AAABDA4A3F2F7B7D1BBC8B /* VisibleRegionMapComponent.swift in Sources */, 0E0AB02ADFBFA812B33DBDEC /* VisibleRegionPage.swift in Sources */, FA0747BF013C9E65676F8838 /* VisibleRegionViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 5D94EFEB29E6E27406CA9711 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BD87DE57C766D0BB384A240 /* CameraRestrictionUITests.swift in Sources */, + CB49E4C305852E171446DA2E /* HeatmapCameraUITests.swift in Sources */, + 4B0BA6BA90381FC6641DD555 /* HereHitTestUITests.swift in Sources */, + B2B112AA7F88521C3FD10FDD /* HolePolygonUITests.swift in Sources */, + 8FB9E5BE76F30E432DCC8174 /* InfoBubbleCustomUITests.swift in Sources */, + E467A41D66C8BF026BD956B7 /* LargeMarkerSwitchUITests.swift in Sources */, + 0AE7FB160024B8B485EB15EB /* LongdoInfoBubbleUITests.swift in Sources */, + 9BE1985582B254F7E7F9E77F /* MarkerDragUITests.swift in Sources */, + 39BD7B6F805E581E3ECD665D /* ProviderSwitchUITests.swift in Sources */, + 7B21D76122110C43BD9D414F /* RasterHeaderUITests.swift in Sources */, + 58EDB1BF6B43FC714812ED94 /* SpiderfyUITests.swift in Sources */, + A955322BEA38C109B9581248 /* StyleReloadUITests.swift in Sources */, + 17B5F2B2856E7710BC987153 /* UISettingsUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 72745BDC03B8123B7F2E5B3E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 59602D30B1131DB7A9B05787 /* MapConductorSampleApp */; + targetProxy = DB8AC8FE011D53CD1E93DE96 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 4E0DC438DF3D8193D5526F63 /* Release */ = { isa = XCBuildConfiguration; @@ -726,6 +1054,44 @@ }; name = Release; }; + 6D4ED19D8231E3674AD4645C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4DUVX7999A; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mapconductor.sample.uitests; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = MapConductorSampleApp; + }; + name = Release; + }; + 6DAE275A4CC828B4AB945530 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4DUVX7999A; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mapconductor.sample.uitests; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = MapConductorSampleApp; + }; + name = Debug; + }; 9A8960D180F904B36B4C1640 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 07E56B15B236BE92BD93461D /* Secrets.xcconfig */; @@ -885,36 +1251,69 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + EF2F9075BD1D92D1704EE3A5 /* Build configuration list for PBXNativeTarget "MapConductorSampleAppUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6DAE275A4CC828B4AB945530 /* Debug */, + 6D4ED19D8231E3674AD4645C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 694460312F7C3E8D00F7B253 /* XCLocalSwiftPackageReference "../../ios-sdk-core" */ = { + 3BAA3B19709CE01C24CAB943 /* XCLocalSwiftPackageReference "../../ios-for-maplibre" */ = { isa = XCLocalSwiftPackageReference; - relativePath = "../../ios-sdk-core"; + relativePath = "../../ios-for-maplibre"; }; - 69815A172F7C41980013BC71 /* XCLocalSwiftPackageReference "../../ios-heatmap" */ = { + 5182F458F2DB02C7AD92C609 /* XCLocalSwiftPackageReference "../../ios-geojson-layer" */ = { isa = XCLocalSwiftPackageReference; - relativePath = "../../ios-heatmap"; + relativePath = "../../ios-geojson-layer"; }; - 69815A1A2F7C41A70013BC71 /* XCLocalSwiftPackageReference "../../ios-marker-cluster" */ = { + 7C7281FBA1BC87C10DADAAAE /* XCLocalSwiftPackageReference "../../ios-for-longdo" */ = { isa = XCLocalSwiftPackageReference; - relativePath = "../../ios-marker-cluster"; + relativePath = "../../ios-for-longdo"; }; - 69815A1D2F7C41C00013BC71 /* XCLocalSwiftPackageReference "../../ios-for-googlemaps" */ = { + 82FF966BE2579177D409F1A3 /* XCLocalSwiftPackageReference "../../ios-for-googlemaps" */ = { isa = XCLocalSwiftPackageReference; relativePath = "../../ios-for-googlemaps"; }; - 69815A202F7C41D00013BC71 /* XCLocalSwiftPackageReference "../../ios-for-mapbox" */ = { + 942816B2270F970F8C62B71B /* XCLocalSwiftPackageReference "../../ios-heatmap" */ = { isa = XCLocalSwiftPackageReference; - relativePath = "../../ios-for-mapbox"; + relativePath = "../../ios-heatmap"; + }; + 9E8A0F65001B9CBC60F693CA /* XCLocalSwiftPackageReference "../../ios-sdk-core" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../ios-sdk-core"; }; - 69815A232F7C41E00013BC71 /* XCLocalSwiftPackageReference "../../ios-for-mapkit" */ = { + A0533065222B487AB4111AB9 /* XCLocalSwiftPackageReference "../../ios-for-mapkit" */ = { isa = XCLocalSwiftPackageReference; relativePath = "../../ios-for-mapkit"; }; - 69815A262F7C41EF0013BC71 /* XCLocalSwiftPackageReference "../../ios-for-maplibre" */ = { + AD164C64C62ED307B56C28E4 /* XCLocalSwiftPackageReference "../../ios-for-arcgis" */ = { isa = XCLocalSwiftPackageReference; - relativePath = "../../ios-for-maplibre"; + relativePath = "../../ios-for-arcgis"; + }; + BD32CF317AE66D8094450F62 /* XCLocalSwiftPackageReference "../../ios-for-tomtom" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../ios-for-tomtom"; + }; + BF0F2FCA7969598D48B37AF1 /* XCLocalSwiftPackageReference "../../ios-for-maptiler" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../ios-for-maptiler"; + }; + EFCDB199203BBDE61A1424B1 /* XCLocalSwiftPackageReference "../../ios-for-mapbox" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../ios-for-mapbox"; + }; + F49D7F82E184630C78F2E126 /* XCLocalSwiftPackageReference "../../ios-marker-cluster" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../ios-marker-cluster"; + }; + FEDBBDD983CCFED337925652 /* XCLocalSwiftPackageReference "../../ios-for-here" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../ios-for-here"; }; /* End XCLocalSwiftPackageReference section */ @@ -938,40 +1337,68 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 69815A182F7C41980013BC71 /* MapConductorHeatmap */ = { + 035F4F1283704F98E8E7C663 /* MapConductorForLongdo */ = { isa = XCSwiftPackageProductDependency; - productName = MapConductorHeatmap; - }; - 69815A1B2F7C41A70013BC71 /* MapConductorMarkerCluster */ = { - isa = XCSwiftPackageProductDependency; - productName = MapConductorMarkerCluster; + productName = MapConductorForLongdo; }; - 69815A1E2F7C41C00013BC71 /* MapConductorForGoogleMaps */ = { + 06C1DBF8CB687729DF1C807A /* MapConductorForGoogleMaps */ = { isa = XCSwiftPackageProductDependency; productName = MapConductorForGoogleMaps; }; - 69815A212F7C41D00013BC71 /* MapConductorForMapbox */ = { + 07F84CB727B4D7BFA9E6BBA9 /* MapConductorForMapbox */ = { isa = XCSwiftPackageProductDependency; productName = MapConductorForMapbox; }; - 69815A242F7C41E00013BC71 /* MapConductorForMapKit */ = { + 5F868FD6810605D51D8DE0E0 /* MapConductorCore */ = { + isa = XCSwiftPackageProductDependency; + productName = MapConductorCore; + }; + 7D730C3AFB56BCACB70D7B43 /* MapConductorForTomTom */ = { + isa = XCSwiftPackageProductDependency; + productName = MapConductorForTomTom; + }; + A2893889C0B45F35A8DB0C4B /* MapConductorGeoJSON */ = { + isa = XCSwiftPackageProductDependency; + productName = MapConductorGeoJSON; + }; + A9471523A3BF989FE54B2446 /* MapConductorForArcGIS */ = { + isa = XCSwiftPackageProductDependency; + productName = MapConductorForArcGIS; + }; + BCE99CF68028363B1329EC64 /* MapConductorForMapKit */ = { isa = XCSwiftPackageProductDependency; productName = MapConductorForMapKit; }; - 69815A272F7C41EF0013BC71 /* MapConductorForMapLibre */ = { + C8D5A9C0CCED6456B3D81D24 /* MapConductorForHERE */ = { isa = XCSwiftPackageProductDependency; - productName = MapConductorForMapLibre; + productName = MapConductorForHERE; }; CC49320174E28DBBB2DA3E3B /* ZIPFoundation */ = { isa = XCSwiftPackageProductDependency; package = EA9904C5DC8D915C46A8309E /* XCRemoteSwiftPackageReference "ZIPFoundation" */; productName = ZIPFoundation; }; + DD849FDD6B93F3EDA511B6F1 /* MapConductorForMapTiler */ = { + isa = XCSwiftPackageProductDependency; + productName = MapConductorForMapTiler; + }; + DE9E4D481C91003D86901307 /* MapConductorForMapLibre */ = { + isa = XCSwiftPackageProductDependency; + productName = MapConductorForMapLibre; + }; + DF824C7DFDFC731E797795D6 /* MapConductorHeatmap */ = { + isa = XCSwiftPackageProductDependency; + productName = MapConductorHeatmap; + }; F67792B83660D03AE335EDCD /* GoogleMaps */ = { isa = XCSwiftPackageProductDependency; package = 27DBF6E8902DBA1D9B9323F0 /* XCRemoteSwiftPackageReference "ios-maps-sdk" */; productName = GoogleMaps; }; + FEB8A11AFC6869DC28064B3D /* MapConductorMarkerCluster */ = { + isa = XCSwiftPackageProductDependency; + productName = MapConductorMarkerCluster; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 027B56AE645A6E8D32E682FE /* Project object */; diff --git a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d8463c9..6f76357 100644 --- a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,11 +1,12 @@ { "pins" : [ { - "identity" : "ios-heatmap", + "identity" : "arcgis-maps-sdk-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/MapConductor/ios-heatmap", + "location" : "https://github.com/Esri/arcgis-maps-sdk-swift", "state" : { - "revision" : "f87531a5311ad6a8f1f6d08ce9ae9d2086adea67" + "revision" : "6926d003e5ab19b0114f9690e6f7f31fdb2a2814", + "version" : "200.8.2" } }, { @@ -18,11 +19,12 @@ } }, { - "identity" : "ios-marker-cluster", + "identity" : "longdo-map-ios-framework", "kind" : "remoteSourceControl", - "location" : "https://github.com/MapConductor/ios-marker-cluster", + "location" : "https://github.com/MetamediaTechnology/longdo-map-ios-framework", "state" : { - "revision" : "b923daccd4876ae571d628da7a1fc04ea0508443" + "revision" : "e76b17a0183c15e9c7738823d6bda99668aa7089", + "version" : "4.1.4" } }, { @@ -30,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mapbox/mapbox-common-ios.git", "state" : { - "revision" : "0a1f567dbd9bec885427928a032569ace4c666e5", - "version" : "24.20.1" + "revision" : "3f75a38a631836aa76f291034a1eac5f614b22c8", + "version" : "24.26.0" } }, { @@ -39,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mapbox/mapbox-core-maps-ios.git", "state" : { - "revision" : "4831764fc4b993856203afe61ff33d962225bf27", - "version" : "11.20.1" + "revision" : "77687efffacd04bd9d22e532bfb4cb71c3ec1e5e", + "version" : "11.26.0" } }, { @@ -48,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mapbox/mapbox-maps-ios-binary", "state" : { - "revision" : "ad7a4d7e52ca2b61c21cd9920ad5c21162ad3245", - "version" : "11.20.1" + "revision" : "9bc931cc1322b118611d533764b0c3f9eea3f609", + "version" : "11.26.0" } }, { @@ -57,8 +59,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/maplibre/maplibre-gl-native-distribution", "state" : { - "revision" : "5d121591b60e837413391814ed88deca2aa7c1d8", - "version" : "6.24.0" + "revision" : "84a79bc375a301169390ac110c868f06c857b83f", + "version" : "6.27.0" + } + }, + { + "identity" : "swifter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/httpswift/swifter", + "state" : { + "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version" : "1.5.0" } }, { diff --git a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcuserdata/masashi.xcuserdatad/IDEFindNavigatorScopes.plist b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcuserdata/masashi.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcuserdata/masashi.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcuserdata/masashi.xcuserdatad/UserInterfaceState.xcuserstate b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcuserdata/masashi.xcuserdatad/UserInterfaceState.xcuserstate index 4ebeeee..9a9c33a 100644 Binary files a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcuserdata/masashi.xcuserdatad/UserInterfaceState.xcuserstate and b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/project.xcworkspace/xcuserdata/masashi.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcshareddata/xcschemes/MapConductorSampleApp.xcscheme b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcshareddata/xcschemes/MapConductorSampleApp.xcscheme index 6fcf38d..84aff41 100644 --- a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcshareddata/xcschemes/MapConductorSampleApp.xcscheme +++ b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcshareddata/xcschemes/MapConductorSampleApp.xcscheme @@ -39,6 +39,17 @@ + + + + diff --git a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcuserdata/masashi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcuserdata/masashi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..041d4c1 --- /dev/null +++ b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcuserdata/masashi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcuserdata/masashi.xcuserdatad/xcschemes/xcschememanagement.plist b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcuserdata/masashi.xcuserdatad/xcschemes/xcschememanagement.plist index 35cfec4..72584bd 100644 --- a/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcuserdata/masashi.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/samples/MapConductorSampleApp/MapConductorSampleApp.xcodeproj/xcuserdata/masashi.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,6 +9,11 @@ orderHint 0 + MapLibreTest (Playground).xcscheme + + orderHint + 6 + diff --git a/samples/MapConductorSampleApp/MapConductorSampleApp.xcworkspace/xcshareddata/swiftpm/Package.resolved b/samples/MapConductorSampleApp/MapConductorSampleApp.xcworkspace/xcshareddata/swiftpm/Package.resolved index bdea15f..defcb1c 100644 --- a/samples/MapConductorSampleApp/MapConductorSampleApp.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/samples/MapConductorSampleApp/MapConductorSampleApp.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "arcgis-maps-sdk-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Esri/arcgis-maps-sdk-swift", + "state" : { + "revision" : "6926d003e5ab19b0114f9690e6f7f31fdb2a2814", + "version" : "200.8.2" + } + }, { "identity" : "ios-maps-sdk", "kind" : "remoteSourceControl", @@ -9,6 +18,15 @@ "version" : "10.7.0" } }, + { + "identity" : "longdo-map-ios-framework", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MetamediaTechnology/longdo-map-ios-framework", + "state" : { + "revision" : "e76b17a0183c15e9c7738823d6bda99668aa7089", + "version" : "4.1.4" + } + }, { "identity" : "mapbox-common-ios", "kind" : "remoteSourceControl", @@ -45,6 +63,15 @@ "version" : "6.24.0" } }, + { + "identity" : "swifter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/httpswift/swifter", + "state" : { + "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version" : "1.5.0" + } + }, { "identity" : "turf-swift", "kind" : "remoteSourceControl", diff --git a/samples/MapConductorSampleApp/Resources/N02-22_GML.zip b/samples/MapConductorSampleApp/Resources/N02-22_GML.zip new file mode 100644 index 0000000..f673fad Binary files /dev/null and b/samples/MapConductorSampleApp/Resources/N02-22_GML.zip differ diff --git a/samples/MapConductorSampleApp/Resources/cluster_red.png b/samples/MapConductorSampleApp/Resources/cluster_red.png new file mode 100644 index 0000000..7fd5c60 Binary files /dev/null and b/samples/MapConductorSampleApp/Resources/cluster_red.png differ diff --git a/samples/MapConductorSampleApp/Sources/ContentView 2.swift b/samples/MapConductorSampleApp/Sources/ContentView 2.swift new file mode 100644 index 0000000..a96b823 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/ContentView 2.swift @@ -0,0 +1,41 @@ +import SwiftUI +import MapConductorForMapLibre +import MapConductorCore +import MapConductorForMapKit +import MapConductorForMapbox + +struct ContentView2: View { + private var mapViewState: MapboxViewState + + private let center = GeoPoint( + latitude: 43.09473984251199, + longitude: 141.51752370787707 + ) + + init() { + + let camera = MapCameraPosition( + position: center, + zoom: 13 + ) + mapViewState = MapboxViewState( + cameraPosition: camera, + ) + } + + var body: some View { + MapboxMapView( + state: mapViewState, + content: { + + Marker( + position: center, + icon: DefaultMarkerIcon( + label: "Japan", + ) + ) + + } + ) + } +} diff --git a/samples/MapConductorSampleApp/Sources/DemoAppScreen.swift b/samples/MapConductorSampleApp/Sources/DemoAppScreen.swift index febb628..b659eae 100644 --- a/samples/MapConductorSampleApp/Sources/DemoAppScreen.swift +++ b/samples/MapConductorSampleApp/Sources/DemoAppScreen.swift @@ -21,29 +21,88 @@ struct DemoAppScreen: View { return "startup" } - private let sidebarItems: [SidebarItem] = [ - SidebarItem(id: "map-basic", title: "Map"), - SidebarItem(id: "map-design", title: "Map Design"), - SidebarItem(id: "map-visibleregion", title: "VisibleRegion"), - SidebarItem(id: "camera-sync", title: "Camera Sync Test"), - SidebarItem(id: "simple-info-bubble", title: "Simple Text Bubble"), - SidebarItem(id: "styled-info-bubble", title: "Custom Styled Bubble"), - SidebarItem(id: "rich-content-info-bubble", title: "Rich Content Bubble"), - SidebarItem(id: "multiple-info-bubbles", title: "Multiple Bubbles"), - SidebarItem(id: "marker-basic", title: "Marker"), - SidebarItem(id: "marker-animation", title: "Marker Animation"), - SidebarItem(id: "marker-postoffice", title: "Bunch of Markers"), - SidebarItem(id: "marker-postoffice-cluster", title: "Marker Cluster"), - SidebarItem(id: "polyline", title: "Polyline"), - SidebarItem(id: "polyline-click", title: "Polyline Click"), - SidebarItem(id: "map-flyto", title: "Fly To"), - SidebarItem(id: "circle", title: "Circle"), - SidebarItem(id: "groundimage", title: "Ground Image"), - SidebarItem(id: "raster-layer", title: "Raster Layer"), - SidebarItem(id: "heatmap", title: "Heatmap"), - SidebarItem(id: "polygon-basic", title: "Polygon"), - SidebarItem(id: "polygon-click", title: "Polygon Click"), - SidebarItem(id: "polygon-geodesic", title: "Polygon Geodesic") + private let sidebarSections: [SidebarSection] = [ + SidebarSection( + id: "map", + title: "Map", + items: [ + SidebarItem(id: "map-basic", title: "Map"), + SidebarItem(id: "map-design", title: "Map Design"), + SidebarItem(id: "map-tilt", title: "Tilt"), + SidebarItem(id: "ui-settings", title: "UI Settings"), + SidebarItem(id: "map-visibleregion", title: "Visible Region"), + SidebarItem(id: "camera-sync", title: "Camera Sync Test"), + SidebarItem(id: "map-flyto", title: "Fly To"), + SidebarItem(id: "map-fitbounds", title: "Fit Bounds"), + SidebarItem(id: "camera-restriction", title: "Camera Restriction") + ] + ), + SidebarSection( + id: "info-bubble", + title: "Info Bubble", + items: [ + SidebarItem(id: "simple-info-bubble", title: "Simple Text Bubble"), + SidebarItem(id: "styled-info-bubble", title: "Custom Styled Bubble"), + SidebarItem(id: "rich-content-info-bubble", title: "Rich Content Bubble"), + SidebarItem(id: "multiple-info-bubbles", title: "Multiple Bubbles") + ] + ), + SidebarSection( + id: "marker", + title: "Marker", + items: [ + SidebarItem(id: "marker-basic", title: "Marker"), + SidebarItem(id: "marker-animation", title: "Marker Animation"), + SidebarItem(id: "marker-postoffice", title: "Bunch of Markers"), + SidebarItem(id: "marker-postoffice-cluster", title: "Marker Cluster") + ] + ), + SidebarSection( + id: "circle", + title: "Circle", + items: [SidebarItem(id: "circle", title: "Circle")] + ), + SidebarSection( + id: "ground-image", + title: "Ground Image", + items: [SidebarItem(id: "groundimage", title: "Ground Image")] + ), + SidebarSection( + id: "polyline", + title: "Polyline", + items: [ + SidebarItem(id: "polyline", title: "Polyline"), + SidebarItem(id: "polyline-click", title: "Polyline Click") + ] + ), + SidebarSection( + id: "polygon", + title: "Polygon", + items: [ + SidebarItem(id: "polygon-basic", title: "Polygon"), + SidebarItem(id: "polygon-click", title: "Polygon Click"), + SidebarItem(id: "polygon-geodesic", title: "Polygon Geodesic"), + SidebarItem(id: "polygon-hole", title: "Polygon Hole") + ] + ), + SidebarSection( + id: "raster-layer", + title: "Raster Layer", + items: [SidebarItem(id: "raster-layer", title: "Raster Layer")] + ), + SidebarSection( + id: "heatmap", + title: "Heatmap", + items: [SidebarItem(id: "heatmap", title: "Heatmap")] + ), + SidebarSection( + id: "geojson", + title: "GeoJSON", + items: [ + SidebarItem(id: "geojson-basic", title: "GeoJSON"), + SidebarItem(id: "geojson-layer", title: "GeoJSON Layer") + ] + ) ] var body: some View { @@ -64,6 +123,10 @@ struct DemoAppScreen: View { StoreMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) case "map-design": MapDesignMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) + case "ui-settings": + UISettingsPage(onToggleSidebar: navigationViewModel.toggleSidebar) + case "map-tilt": + TiltMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) case "map-visibleregion": VisibleRegionPage(onToggleSidebar: navigationViewModel.toggleSidebar) case "camera-sync": @@ -82,6 +145,10 @@ struct DemoAppScreen: View { PolylineClickMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) case "map-flyto": FlyToMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) + case "map-fitbounds": + FitBoundsMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) + case "camera-restriction": + CameraRestrictionPage(onToggleSidebar: navigationViewModel.toggleSidebar) case "circle": CircleMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) case "groundimage": @@ -90,19 +157,31 @@ struct DemoAppScreen: View { RasterLayerMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) case "heatmap": HeatmapMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) + case "geojson-basic": + BasicGeoJSONMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) + case "geojson-layer": + GeoJSONLayerMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) case "polygon-basic": PolygonMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) case "polygon-click": PolygonClickMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) case "polygon-geodesic": PolygonGeodesicPage(onToggleSidebar: navigationViewModel.toggleSidebar) + case "polygon-hole": + HolePolygonMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) + case "here-hittest": + HereHitTestPage(onToggleSidebar: navigationViewModel.toggleSidebar) + case "style-reload": + StyleReloadPage(onToggleSidebar: navigationViewModel.toggleSidebar) + case "raster-header": + RasterHeaderPage(onToggleSidebar: navigationViewModel.toggleSidebar) default: StoreMapPage(onToggleSidebar: navigationViewModel.toggleSidebar) } } Sidebar( - items: sidebarItems, + sections: sidebarSections, selectedItemId: navigationViewModel.currentPage, onItemClick: { item in navigationViewModel.navigateTo(item.id) diff --git a/samples/MapConductorSampleApp/Sources/SampleConfig.swift b/samples/MapConductorSampleApp/Sources/SampleConfig.swift index 5287707..b39ad99 100644 --- a/samples/MapConductorSampleApp/Sources/SampleConfig.swift +++ b/samples/MapConductorSampleApp/Sources/SampleConfig.swift @@ -1,20 +1,75 @@ import Foundation enum SampleConfig { - static var googleMapsApiKey: String { + static var googleMapsApiKey: String? { let key = "GOOGLE_MAPS_API_KEY" guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String, !value.isEmpty else { - fatalError("Missing \(key). Set it in Config/Secrets.xcconfig.") + return nil } return value } - static var mapboxAccessToken: String { + static var mapboxAccessToken: String? { let key = "MAPBOX_ACCESS_TOKEN" guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String, !value.isEmpty else { - fatalError("Missing \(key). Set it in Config/Secrets.xcconfig.") + return nil + } + return value + } + + static var arcGISApiKey: String? { + let key = "ARCGIS_API_KEY" + guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String, + !value.isEmpty, + !value.contains("$(") else { + return nil + } + return value + } + static var hereAccessKeyId: String? { + let key = "HERE_ACCESS_KEY_ID" + guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String, + !value.isEmpty, + !value.contains("$(") else { + return nil + } + return value + } + static var hereAccessKeySecret: String? { + let key = "HERE_ACCESS_KEY_SECRET" + guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String, + !value.isEmpty, + !value.contains("$(") else { + return nil + } + return value + } + static var tomTomApiKey: String? { + let key = "TOMTOM_API_KEY" + guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String, + !value.isEmpty, + !value.contains("$(") else { + return nil + } + return value + } + static var mapTilerApiKey: String? { + let key = "MapTilerAPIKey" + guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String, + !value.isEmpty, + !value.contains("$(") else { + return nil + } + return value + } + static var longdoApiKey: String? { + let key = "LONGDO_API_KEY" + guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String, + !value.isEmpty, + !value.contains("$(") else { + return nil } return value } diff --git a/samples/MapConductorSampleApp/Sources/pages/camerasync/CameraSyncTestPage.swift b/samples/MapConductorSampleApp/Sources/pages/camerasync/CameraSyncTestPage.swift index 5a1937f..1176485 100644 --- a/samples/MapConductorSampleApp/Sources/pages/camerasync/CameraSyncTestPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/camerasync/CameraSyncTestPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI import GoogleMaps @@ -14,48 +19,76 @@ struct CameraSyncTestPage: View { @StateObject private var leftMapLibreState: MapLibreViewState @StateObject private var leftMapKitState: MapKitViewState @StateObject private var leftMapboxState: MapboxViewState + @StateObject private var leftArcGISState: ArcGISMapViewState + @StateObject private var leftHereState: HereMapViewState + @StateObject private var leftTomTomState: TomTomMapViewState + @StateObject private var leftMapTilerState: MapTilerViewState + @StateObject private var leftLongdoState: LongdoViewState @StateObject private var rightGoogleState: GoogleMapViewState @StateObject private var rightMapLibreState: MapLibreViewState @StateObject private var rightMapKitState: MapKitViewState @StateObject private var rightMapboxState: MapboxViewState + @StateObject private var rightArcGISState: ArcGISMapViewState + @StateObject private var rightHereState: HereMapViewState + @StateObject private var rightTomTomState: TomTomMapViewState + @StateObject private var rightMapTilerState: MapTilerViewState + @StateObject private var rightLongdoState: LongdoViewState + + // Camera state displayed in the info panels + @State private var leftCameraPosition: MapCameraPosition + @State private var rightCameraPosition: MapCameraPosition + + // Map view size (points) displayed in the info panels + @State private var leftMapSize: CGSize = .zero + @State private var rightMapSize: CGSize = .zero + + // Feedback-loop guard: when we move a map programmatically, ignore the resulting + // camera callbacks until the move settles (mirrors Android's programmatic key logic). + @State private var programmaticLeftKey: Int64? = nil + @State private var programmaticLeftTarget: MapCameraPosition? = nil + @State private var programmaticLeftUntilMs: Double = 0 + @State private var programmaticLeftGraceUntilMs: Double = 0 + + @State private var programmaticRightKey: Int64? = nil + @State private var programmaticRightTarget: MapCameraPosition? = nil + @State private var programmaticRightUntilMs: Double = 0 + @State private var programmaticRightGraceUntilMs: Double = 0 + + // Move-time throttle so we don't overwhelm the target SDK (~30 fps). + @State private var lastLeftMoveSyncAtMs: Double = 0 + @State private var lastRightMoveSyncAtMs: Double = 0 + + private let programmaticTtlMs: Double = 1200 + private let programmaticGraceMs: Double = 250 + private let moveSyncIntervalMs: Double = 33 init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar let vm = CameraSyncTestViewModel() _viewModel = StateObject(wrappedValue: vm) - - // Left map states - _leftGoogleState = StateObject(wrappedValue: GoogleMapViewState( - cameraPosition: vm.initCameraPosition - )) - _leftMapLibreState = StateObject(wrappedValue: MapLibreViewState( - mapDesignType: MapLibreDesign.OsmBright, - cameraPosition: vm.initCameraPosition - )) - _leftMapKitState = StateObject(wrappedValue: MapKitViewState( - mapDesignType: MapKitMapDesign.Standard, - cameraPosition: vm.initCameraPosition - )) - _leftMapboxState = StateObject(wrappedValue: MapboxViewState( - cameraPosition: vm.initCameraPosition - )) - - // Right map states - _rightGoogleState = StateObject(wrappedValue: GoogleMapViewState( - cameraPosition: vm.initCameraPosition - )) - _rightMapLibreState = StateObject(wrappedValue: MapLibreViewState( - mapDesignType: MapLibreDesign.OsmBright, - cameraPosition: vm.initCameraPosition - )) - _rightMapKitState = StateObject(wrappedValue: MapKitViewState( - mapDesignType: MapKitMapDesign.Standard, - cameraPosition: vm.initCameraPosition - )) - _rightMapboxState = StateObject(wrappedValue: MapboxViewState( - cameraPosition: vm.initCameraPosition - )) + _leftCameraPosition = State(initialValue: vm.initCameraPosition) + _rightCameraPosition = State(initialValue: vm.initCameraPosition) + + _leftGoogleState = StateObject(wrappedValue: GoogleMapViewState(cameraPosition: vm.initCameraPosition)) + _leftMapLibreState = StateObject(wrappedValue: MapLibreViewState(mapDesignType: MapLibreDesign.OsmBright, cameraPosition: vm.initCameraPosition)) + _leftMapKitState = StateObject(wrappedValue: MapKitViewState(mapDesignType: MapKitMapDesign.Standard, cameraPosition: vm.initCameraPosition)) + _leftMapboxState = StateObject(wrappedValue: MapboxViewState(cameraPosition: vm.initCameraPosition)) + _leftArcGISState = StateObject(wrappedValue: ArcGISMapViewState(mapDesignType: ArcGISDesign.OsmStandard, cameraPosition: vm.initCameraPosition)) + _leftHereState = StateObject(wrappedValue: HereMapViewState(mapDesignType: HereMapDesign.NormalDay, cameraPosition: vm.initCameraPosition)) + _leftTomTomState = StateObject(wrappedValue: TomTomMapViewState(mapDesignType: TomTomMapDesign.Standard, cameraPosition: vm.initCameraPosition)) + _leftMapTilerState = StateObject(wrappedValue: MapTilerViewState(mapDesignType: MapTilerDesign.Streets, cameraPosition: vm.initCameraPosition)) + _leftLongdoState = StateObject(wrappedValue: LongdoViewState(mapDesignType: LongdoDesign.Normal, cameraPosition: vm.initCameraPosition)) + + _rightGoogleState = StateObject(wrappedValue: GoogleMapViewState(cameraPosition: vm.initCameraPosition)) + _rightMapLibreState = StateObject(wrappedValue: MapLibreViewState(mapDesignType: MapLibreDesign.OsmBright, cameraPosition: vm.initCameraPosition)) + _rightMapKitState = StateObject(wrappedValue: MapKitViewState(mapDesignType: MapKitMapDesign.Standard, cameraPosition: vm.initCameraPosition)) + _rightMapboxState = StateObject(wrappedValue: MapboxViewState(cameraPosition: vm.initCameraPosition)) + _rightArcGISState = StateObject(wrappedValue: ArcGISMapViewState(mapDesignType: ArcGISDesign.OsmStandard, cameraPosition: vm.initCameraPosition)) + _rightHereState = StateObject(wrappedValue: HereMapViewState(mapDesignType: HereMapDesign.NormalDay, cameraPosition: vm.initCameraPosition)) + _rightTomTomState = StateObject(wrappedValue: TomTomMapViewState(mapDesignType: TomTomMapDesign.Standard, cameraPosition: vm.initCameraPosition)) + _rightMapTilerState = StateObject(wrappedValue: MapTilerViewState(mapDesignType: MapTilerDesign.Streets, cameraPosition: vm.initCameraPosition)) + _rightLongdoState = StateObject(wrappedValue: LongdoViewState(mapDesignType: LongdoDesign.Normal, cameraPosition: vm.initCameraPosition)) } var body: some View { @@ -77,29 +110,25 @@ struct CameraSyncTestPage: View { Spacer() HStack(spacing: 20) { - // Left provider picker VStack(alignment: .leading, spacing: 4) { Text("Left (Source)") .font(.caption) .foregroundColor(.secondary) Picker("Left", selection: $viewModel.leftProvider) { ForEach(MapProvider.allCases) { provider in - Text(viewModel.getProviderName(provider)) - .tag(provider) + Text(viewModel.getProviderName(provider)).tag(provider) } } .pickerStyle(.menu) } - // Right provider picker VStack(alignment: .leading, spacing: 4) { Text("Right (Synced)") .font(.caption) .foregroundColor(.secondary) Picker("Right", selection: $viewModel.rightProvider) { ForEach(MapProvider.allCases) { provider in - Text(viewModel.getProviderName(provider)) - .tag(provider) + Text(viewModel.getProviderName(provider)).tag(provider) } } .pickerStyle(.menu) @@ -110,344 +139,476 @@ struct CameraSyncTestPage: View { .background(Color(UIColor.systemBackground)) // Location navigation buttons - HStack(spacing: 8) { - ForEach(0.. geometry.size.width + if isPortrait { + VStack(spacing: 0) { + mapPane(side: .left) + Divider() + mapPane(side: .right) + } + } else { + HStack(spacing: 0) { + mapPane(side: .left) + Divider() + mapPane(side: .right) + } } + } + } + } - Divider() - - // Right map (synced) - VStack(spacing: 0) { - mapView( - provider: viewModel.rightProvider, - googleState: rightGoogleState, - mapLibreState: rightMapLibreState, - mapKitState: rightMapKitState, - mapboxState: rightMapboxState, - isSource: false - ) - - cameraInfoView( - provider: viewModel.rightProvider, - googleState: rightGoogleState, - mapLibreState: rightMapLibreState, - mapKitState: rightMapKitState, - mapboxState: rightMapboxState, - label: "Synced Camera" - ) - } + // MARK: - Map pane + + @ViewBuilder + private func mapPane(side: ActiveMapPane) -> some View { + let provider = side == .left ? viewModel.leftProvider : viewModel.rightProvider + ZStack(alignment: .bottomLeading) { + buildMapView( + provider: provider, + side: side + ) + cameraInfoPanel( + side: side, + label: side == .left ? "Source Camera" : "Synced Camera" + ) + .padding(8) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background( + GeometryReader { proxy in + Color.clear + .onAppear { updateMapSize(side, proxy.size) } + .onChange(of: proxy.size) { newValue in updateMapSize(side, newValue) } } + ) + } + + private func updateMapSize(_ side: ActiveMapPane, _ size: CGSize) { + switch side { + case .left where leftMapSize != size: leftMapSize = size + case .right where rightMapSize != size: rightMapSize = size + default: break } } @ViewBuilder - private func mapView( - provider: MapProvider, - googleState: GoogleMapViewState, - mapLibreState: MapLibreViewState, - mapKitState: MapKitViewState, - mapboxState: MapboxViewState, - isSource: Bool - ) -> some View { + private func buildMapView(provider: MapProvider, side: ActiveMapPane) -> some View { + let onMove: OnCameraMoveHandler = { position in handleCameraMove(position, from: side) } + let onMoveEnd: OnCameraMoveHandler = { position in handleCameraMoveEnd(position, from: side) } + switch provider { case .googleMaps: - GoogleMapView( - state: googleState, - onCameraMoveEnd: isSource ? { position in - syncToRight(position) - } : nil, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } - ) { - mapContent() + if let apiKey = SampleConfig.googleMapsApiKey { + GoogleMapView( + state: side == .left ? leftGoogleState : rightGoogleState, + onCameraMove: onMove, + onCameraMoveEnd: onMoveEnd, + sdkInitialize: { GMSServices.provideAPIKey(apiKey) } + ) { mapContent() } + } else { + Text("Google Maps is not available due to no API key") } case .mapLibre: MapLibreMapView( - state: mapLibreState, - onCameraMoveEnd: isSource ? { position in - syncToRight(position) - } : nil - ) { - mapContent() - } + state: side == .left ? leftMapLibreState : rightMapLibreState, + onCameraMove: onMove, + onCameraMoveEnd: onMoveEnd + ) { mapContent() } case .mapKit: MapKitMapView( - state: mapKitState, - onCameraMoveEnd: isSource ? { position in - syncToRight(position) - } : nil - ) { - mapContent() - } + state: side == .left ? leftMapKitState : rightMapKitState, + onCameraMove: onMove, + onCameraMoveEnd: onMoveEnd + ) { mapContent() } case .mapbox: - MapboxMapView( - state: mapboxState, - onCameraMoveEnd: isSource ? { position in - syncToRight(position) - } : nil - ) { - mapContent() + if let accessToken = SampleConfig.mapboxAccessToken { + MapboxMapView( + state: side == .left ? leftMapboxState : rightMapboxState, + onCameraMove: onMove, + onCameraMoveEnd: onMoveEnd, + sdkInitialize: { initializeMapbox(accessToken: accessToken) }, + content: { mapContent() } + ) + } else { + Text("Mapbox is not available due to no access token") } - } - } - @ViewBuilder - private func cameraInfoView( - provider: MapProvider, - googleState: GoogleMapViewState, - mapLibreState: MapLibreViewState, - mapKitState: MapKitViewState, - mapboxState: MapboxViewState, - label: String - ) -> some View { - Group { - let position = getCameraPosition( - provider: provider, - googleState: googleState, - mapLibreState: mapLibreState, - mapKitState: mapKitState, - mapboxState: mapboxState - ) + case .arcGIS: + if let apiKey = SampleConfig.arcGISApiKey { + ArcGISMapView( + state: side == .left ? leftArcGISState : rightArcGISState, + onCameraMove: onMove, + onCameraMoveEnd: onMoveEnd, + sdkInitialize: { _ = arcGISApiKeyInitialize(apiKey: apiKey) } + ) { mapContent() } + } else { + Text("ArcGIS is not available due to no API key") + } - VStack(alignment: .leading, spacing: 4) { - Text(label) - .font(.caption) - .fontWeight(.bold) - .foregroundColor(.primary) - - HStack(spacing: 16) { - VStack(alignment: .leading, spacing: 2) { - Text("Lat: \(String(format: "%.5f", position.position.latitude))") - .font(.caption2) - Text("Lng: \(String(format: "%.5f", position.position.longitude))") - .font(.caption2) + case .arcGIS2D: + if let apiKey = SampleConfig.arcGISApiKey { + // 2D(MapView)と 3D(SceneView)は同じ ArcGISMapViewState を共有する。 + // 1 ペインに同時に描画されることはないので状態を分ける必要はない + // (SampleMapView と同じ方針)。 + ArcGISMapView2D( + state: side == .left ? leftArcGISState : rightArcGISState, + onCameraMove: onMove, + onCameraMoveEnd: onMoveEnd, + sdkInitialize: { _ = arcGISApiKeyInitialize(apiKey: apiKey) } + ) { mapContent() } + } else { + Text("ArcGIS is not available due to no API key") + } + + case .here: + if let accessKey = SampleConfig.hereAccessKeyId, + let accessSecret = SampleConfig.hereAccessKeySecret { + HereMapView( + state: side == .left ? leftHereState : rightHereState, + onCameraMove: onMove, + onCameraMoveEnd: onMoveEnd, + sdkInitialize: { + do { + try hereKeyInitialize(accessKeyId: accessKey, accessKeySecret: accessSecret) + } catch { + NSLog("[MapConductor] HERE authentication failed: %@", String(describing: error)) + } } + ) { mapContent() } + } else { + Text("Here is not available due to no API key") + } + case .tomTom: + if let apiKey = SampleConfig.tomTomApiKey { + TomTomMapView( + state: side == .left ? leftTomTomState : rightTomTomState, + apiKey: apiKey, + onCameraMove: onMove, + onCameraMoveEnd: onMoveEnd + ) { mapContent() } + } else { + Text("TomTom is not available due to no API key") + } + case .mapTiler: + if let apiKey = SampleConfig.mapTilerApiKey { + MapTilerMapView( + state: side == .left ? leftMapTilerState : rightMapTilerState, + apiKey: apiKey, + onCameraMove: onMove, + onCameraMoveEnd: onMoveEnd + ) { mapContent() } + } else { + Text("MapTiler is not available due to no API key") + } + case .longdo: + if let apiKey = SampleConfig.longdoApiKey { + LongdoMapView( + state: side == .left ? leftLongdoState : rightLongdoState, + apiKey: apiKey, + onCameraMove: onMove, + onCameraMoveEnd: onMoveEnd + ) { mapContent() } + } else { + Text("Longdo is not available due to no API key") + } + } + } - VStack(alignment: .leading, spacing: 2) { - Text("Zoom: \(String(format: "%.2f", position.zoom))") - .font(.caption2) - Text("Tilt: \(String(format: "%.1f°", position.tilt))") - .font(.caption2) + // MARK: - Sync logic + + private func handleCameraMove(_ position: MapCameraPosition, from side: ActiveMapPane) { + let now = nowMs() + NSLog("[CameraSync] move %@ z=%.2f lat=%.5f lng=%.5f", + side == .left ? "L" : "R", position.zoom, position.position.latitude, position.position.longitude) + switch side { + case .left: + if programmaticLeftKey != nil { + if now > programmaticLeftUntilMs { + clearProgrammatic(.left) + } else { + if now <= programmaticLeftGraceUntilMs || isProgrammaticMove(.left, position, now) { + NSLog("[CameraSync] L suppressed (programmatic echo)") + leftCameraPosition = position + return + } + NSLog("[CameraSync] L programmatic guard CLEARED (not close to target)") + clearProgrammatic(.left) + } + } + if now - lastLeftMoveSyncAtMs < moveSyncIntervalMs { return } + lastLeftMoveSyncAtMs = now + leftCameraPosition = position + rightCameraPosition = position + markProgrammatic(.right, target: position, now: now) + NSLog("[CameraSync] sync L→R apply z=%.2f", position.zoom) + moveRight(to: position, duration: 0) + + case .right: + if programmaticRightKey != nil { + if now > programmaticRightUntilMs { + clearProgrammatic(.right) + } else { + if now <= programmaticRightGraceUntilMs || isProgrammaticMove(.right, position, now) { + NSLog("[CameraSync] R suppressed (programmatic echo)") + rightCameraPosition = position + return } + NSLog("[CameraSync] R programmatic guard CLEARED (not close to target)") + clearProgrammatic(.right) + } + } + if now - lastRightMoveSyncAtMs < moveSyncIntervalMs { return } + lastRightMoveSyncAtMs = now + rightCameraPosition = position + leftCameraPosition = position + markProgrammatic(.left, target: position, now: now) + NSLog("[CameraSync] sync R→L apply z=%.2f", position.zoom) + moveLeft(to: position, duration: 0) + } + } - VStack(alignment: .leading, spacing: 2) { - Text("Bearing: \(String(format: "%.1f°", position.bearing))") - .font(.caption2) - Text("Alt: \(String(format: "%.0f m", position.position.altitude ?? 0))") - .font(.caption2) + private func handleCameraMoveEnd(_ position: MapCameraPosition, from side: ActiveMapPane) { + let now = nowMs() + switch side { + case .left: + if programmaticLeftKey != nil { + if now > programmaticLeftUntilMs { + clearProgrammatic(.left) + } else { + if now <= programmaticLeftGraceUntilMs || isProgrammaticMove(.left, position, now) { + leftCameraPosition = position + return } + clearProgrammatic(.left) } } - .padding(8) - .frame(maxWidth: .infinity, alignment: .leading) - .background(Color(UIColor.secondarySystemBackground)) + leftCameraPosition = position + rightCameraPosition = position + markProgrammatic(.right, target: position, now: now) + NSLog("[CameraSync] moveEnd sync L→R apply z=%.2f", position.zoom) + moveRight(to: position, duration: 0) + + case .right: + if programmaticRightKey != nil { + if now > programmaticRightUntilMs { + clearProgrammatic(.right) + } else { + if now <= programmaticRightGraceUntilMs || isProgrammaticMove(.right, position, now) { + rightCameraPosition = position + return + } + clearProgrammatic(.right) + } + } + rightCameraPosition = position + leftCameraPosition = position + markProgrammatic(.left, target: position, now: now) + NSLog("[CameraSync] moveEnd sync R→L apply z=%.2f", position.zoom) + moveLeft(to: position, duration: 0) } } - private func getCameraPosition( - provider: MapProvider, - googleState: GoogleMapViewState, - mapLibreState: MapLibreViewState, - mapKitState: MapKitViewState, - mapboxState: MapboxViewState - ) -> MapCameraPosition { - switch provider { - case .googleMaps: - return googleState.cameraPosition - case .mapLibre: - return mapLibreState.cameraPosition - case .mapKit: - return mapKitState.cameraPosition - case .mapbox: - return mapboxState.cameraPosition + // MARK: - Programmatic move helpers + + private func cameraKey(_ camera: MapCameraPosition) -> Int64 { + let latE5 = Int64(camera.position.latitude * 1e5) + let lonE5 = Int64(camera.position.longitude * 1e5) + let zoom100 = Int64(camera.zoom * 100) + let bearing10 = Int64(camera.bearing * 10) + return ((latE5 * 31 &+ lonE5) * 31 &+ zoom100) * 31 &+ bearing10 + } + + private func bearingDelta(_ a: Double, _ b: Double) -> Double { + let d = ((a - b).truncatingRemainder(dividingBy: 360) + 360).truncatingRemainder(dividingBy: 360) + return d > 180 ? 360 - d : d + } + + private func isCloseToTarget(_ camera: MapCameraPosition, _ target: MapCameraPosition) -> Bool { + abs(camera.position.latitude - target.position.latitude) < 0.0012 && + abs(camera.position.longitude - target.position.longitude) < 0.0012 && + abs(camera.zoom - target.zoom) < 0.75 && + bearingDelta(camera.bearing, target.bearing) < 12 && + abs(camera.tilt - target.tilt) < 6 + } + + private func isProgrammaticMove(_ side: ActiveMapPane, _ camera: MapCameraPosition, _ now: Double) -> Bool { + let (key, target, until): (Int64?, MapCameraPosition?, Double) + switch side { + case .left: (key, target, until) = (programmaticLeftKey, programmaticLeftTarget, programmaticLeftUntilMs) + case .right: (key, target, until) = (programmaticRightKey, programmaticRightTarget, programmaticRightUntilMs) } + guard key != nil else { return false } + if now > until { return false } + if cameraKey(camera) == key { return true } + return target.map { isCloseToTarget(camera, $0) } ?? false } - private func syncToRight(_ position: MapCameraPosition) { + private func markProgrammatic(_ side: ActiveMapPane, target: MapCameraPosition, now: Double) { + let key = cameraKey(target) + switch side { + case .left: + programmaticLeftKey = key + programmaticLeftTarget = target + programmaticLeftGraceUntilMs = now + programmaticGraceMs + programmaticLeftUntilMs = now + programmaticTtlMs + case .right: + programmaticRightKey = key + programmaticRightTarget = target + programmaticRightGraceUntilMs = now + programmaticGraceMs + programmaticRightUntilMs = now + programmaticTtlMs + } + } + + private func clearProgrammatic(_ side: ActiveMapPane) { + switch side { + case .left: + programmaticLeftKey = nil; programmaticLeftTarget = nil + programmaticLeftGraceUntilMs = 0; programmaticLeftUntilMs = 0 + case .right: + programmaticRightKey = nil; programmaticRightTarget = nil + programmaticRightGraceUntilMs = 0; programmaticRightUntilMs = 0 + } + } + + // MARK: - Map movement + + private func moveLeft(to position: MapCameraPosition, duration: Int64) { + switch viewModel.leftProvider { + case .googleMaps: leftGoogleState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .mapLibre: leftMapLibreState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .mapKit: leftMapKitState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .mapbox: leftMapboxState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .arcGIS, .arcGIS2D: leftArcGISState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .here: leftHereState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .tomTom: leftTomTomState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .mapTiler: leftMapTilerState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .longdo: leftLongdoState.moveCameraTo(cameraPosition: position, durationMillis: duration) + } + } + + private func moveRight(to position: MapCameraPosition, duration: Int64) { switch viewModel.rightProvider { - case .googleMaps: - viewModel.onLeftCameraChange(position, rightState: rightGoogleState) - case .mapLibre: - viewModel.onLeftCameraChange(position, rightState: rightMapLibreState) - case .mapKit: - viewModel.onLeftCameraChange(position, rightState: rightMapKitState) - case .mapbox: - viewModel.onLeftCameraChange(position, rightState: rightMapboxState) + case .googleMaps: rightGoogleState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .mapLibre: rightMapLibreState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .mapKit: rightMapKitState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .mapbox: rightMapboxState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .arcGIS, .arcGIS2D: rightArcGISState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .here: rightHereState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .tomTom: rightTomTomState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .mapTiler: rightMapTilerState.moveCameraTo(cameraPosition: position, durationMillis: duration) + case .longdo: rightLongdoState.moveCameraTo(cameraPosition: position, durationMillis: duration) } } - @MapViewContentBuilder - private func mapContent() -> MapViewContent { - // Draw bounds polylines for each location - let location0 = viewModel.locations[0] - let bounds0 = location0.bounds - Polyline( - state: PolylineState( - points: [ - bounds0.southWest!, - GeoPoint(latitude: bounds0.southWest!.latitude, longitude: bounds0.northEast!.longitude, altitude: 0), - bounds0.northEast!, - GeoPoint(latitude: bounds0.northEast!.latitude, longitude: bounds0.southWest!.longitude, altitude: 0), - bounds0.southWest! - ], - strokeColor: .systemRed, - strokeWidth: 3.0, - geodesic: true - ) - ) + // MARK: - Location navigation - let location1 = viewModel.locations[1] - let bounds1 = location1.bounds - Polyline( - state: PolylineState( - points: [ - bounds1.southWest!, - GeoPoint(latitude: bounds1.southWest!.latitude, longitude: bounds1.northEast!.longitude, altitude: 0), - bounds1.northEast!, - GeoPoint(latitude: bounds1.northEast!.latitude, longitude: bounds1.southWest!.longitude, altitude: 0), - bounds1.southWest! - ], - strokeColor: .systemRed, - strokeWidth: 3.0, - geodesic: true - ) - ) + private func navigateToLocation(_ index: Int) { + let location = viewModel.locations[index] + let position = MapCameraPosition(position: location.center, zoom: location.zoom, bearing: 0.0, tilt: 0.0) + let now = nowMs() + // Arm the guards BEFORE issuing the moves: moveRight can block on the Longdo WKWebView + // bridge, and the runloop keeps delivering the source map's camera events during that + // wait — with the guards not yet set, those events would sync the fly-path onto the + // synced map (visible on Longdo as several discrete zoom steps before settling). + leftCameraPosition = position + rightCameraPosition = position + markProgrammatic(.left, target: position, now: now) + markProgrammatic(.right, target: position, now: now) + // Extend the guard to cover the full 1s animation window. The grace must span the whole + // animation too: mid-flight cameras are far from the final target, so the closeness check + // alone would release the guard mid-animation. + programmaticLeftUntilMs = now + 1000 + programmaticTtlMs + programmaticRightUntilMs = now + 1000 + programmaticTtlMs + programmaticLeftGraceUntilMs = now + 1000 + programmaticGraceMs + programmaticRightGraceUntilMs = now + 1000 + programmaticGraceMs + NSLog("[CameraSync] navigateTo idx=%d z=%.2f (guards armed)", index, position.zoom) + moveLeft(to: position, duration: 1000) + moveRight(to: position, duration: 1000) + } - let location2 = viewModel.locations[2] - let bounds2 = location2.bounds - Polyline( - state: PolylineState( - points: [ - bounds2.southWest!, - GeoPoint(latitude: bounds2.southWest!.latitude, longitude: bounds2.northEast!.longitude, altitude: 0), - bounds2.northEast!, - GeoPoint(latitude: bounds2.northEast!.latitude, longitude: bounds2.southWest!.longitude, altitude: 0), - bounds2.southWest! - ], - strokeColor: .systemRed, - strokeWidth: 3.0, - geodesic: true - ) - ) + // MARK: - Camera info panel - let location3 = viewModel.locations[3] - let bounds3 = location3.bounds - Polyline( - state: PolylineState( - points: [ - bounds3.southWest!, - GeoPoint(latitude: bounds3.southWest!.latitude, longitude: bounds3.northEast!.longitude, altitude: 0), - bounds3.northEast!, - GeoPoint(latitude: bounds3.northEast!.latitude, longitude: bounds3.southWest!.longitude, altitude: 0), - bounds3.southWest! - ], - strokeColor: .systemRed, - strokeWidth: 3.0, - geodesic: true - ) - ) + @ViewBuilder + private func cameraInfoPanel(side: ActiveMapPane, label: String) -> some View { + let position = side == .left ? leftCameraPosition : rightCameraPosition + let mapSize = side == .left ? leftMapSize : rightMapSize + VStack(alignment: .leading, spacing: 4) { + Text(label) + .font(.caption) + .fontWeight(.bold) + HStack(spacing: 12) { + VStack(alignment: .leading, spacing: 2) { + Text("Lat: \(String(format: "%.5f", position.position.latitude))").font(.caption2) + Text("Zoom: \(String(format: "%.2f", position.zoom))").font(.caption2) + } + VStack(alignment: .leading, spacing: 2) { + Text("Alt: \(String(format: "%.0f m", position.position.altitude ?? 0))").font(.caption2) + Text("Size: \(String(format: "%.0f × %.0f pt", mapSize.width, mapSize.height))").font(.caption2) + } + } + } + .padding(8) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8)) + } - let location4 = viewModel.locations[4] - let bounds4 = location4.bounds - Polyline( - state: PolylineState( + // MARK: - Map overlays + + @MapViewContentBuilder + private func mapContent() -> MapViewContent { + ForArray(viewModel.locations) { location in + let sw = location.bounds.southWest! + let ne = location.bounds.northEast! + Polyline(state: PolylineState( points: [ - bounds4.southWest!, - GeoPoint(latitude: bounds4.southWest!.latitude, longitude: bounds4.northEast!.longitude, altitude: 0), - bounds4.northEast!, - GeoPoint(latitude: bounds4.northEast!.latitude, longitude: bounds4.southWest!.longitude, altitude: 0), - bounds4.southWest! + sw, + GeoPoint(latitude: sw.latitude, longitude: ne.longitude, altitude: 0), + ne, + GeoPoint(latitude: ne.latitude, longitude: sw.longitude, altitude: 0), + sw, ], strokeColor: .systemRed, strokeWidth: 3.0, geodesic: true - ) - ) - - // Draw reference rectangles for zoom calibration - let rectangles = viewModel.getReferenceRectangles() - Polygon(state: rectangles[0]) - Polygon(state: rectangles[1]) - Polygon(state: rectangles[2]) - Polygon(state: rectangles[3]) - Polygon(state: rectangles[4]) + )) + } + ForArray(viewModel.getReferenceRectangles()) { rect in + Polygon(state: rect) + } } - private func navigateToLocation(_ index: Int) { - let location = viewModel.locations[index] - let position = MapCameraPosition( - position: location.center, - zoom: location.zoom, - bearing: 0.0, - tilt: 0.0 - ) - - // Navigate both maps - switch viewModel.leftProvider { - case .googleMaps: - leftGoogleState.moveCameraTo(cameraPosition: position, durationMillis: 1000) - case .mapLibre: - leftMapLibreState.moveCameraTo(cameraPosition: position, durationMillis: 1000) - case .mapKit: - leftMapKitState.moveCameraTo(cameraPosition: position, durationMillis: 1000) - case .mapbox: - leftMapboxState.moveCameraTo(cameraPosition: position, durationMillis: 1000) - } + // MARK: - Utilities - switch viewModel.rightProvider { - case .googleMaps: - rightGoogleState.moveCameraTo(cameraPosition: position, durationMillis: 1000) - case .mapLibre: - rightMapLibreState.moveCameraTo(cameraPosition: position, durationMillis: 1000) - case .mapKit: - rightMapKitState.moveCameraTo(cameraPosition: position, durationMillis: 1000) - case .mapbox: - rightMapboxState.moveCameraTo(cameraPosition: position, durationMillis: 1000) - } + private func nowMs() -> Double { + ProcessInfo.processInfo.systemUptime * 1000 } } + +private enum ActiveMapPane { + case left, right +} diff --git a/samples/MapConductorSampleApp/Sources/pages/camerasync/CameraSyncTestViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/camerasync/CameraSyncTestViewModel.swift index c586a39..18b0a3a 100644 --- a/samples/MapConductorSampleApp/Sources/pages/camerasync/CameraSyncTestViewModel.swift +++ b/samples/MapConductorSampleApp/Sources/pages/camerasync/CameraSyncTestViewModel.swift @@ -11,8 +11,14 @@ struct CameraLocationInfo { @MainActor class CameraSyncTestViewModel: ObservableObject { - @Published var leftProvider: MapProvider = .googleMaps - @Published var rightProvider: MapProvider = .mapKit + /// 左右のプロバイダは環境変数でも指定できる(UI テスト用)。 + /// 左は他ページと同じ `MAPCONDUCTOR_SAMPLE_PROVIDER`、右は `..._RIGHT`。 + @Published var leftProvider: MapProvider = .initial(fallback: .googleMaps) + @Published var rightProvider: MapProvider = .initial( + environmentKey: "MAPCONDUCTOR_SAMPLE_PROVIDER_RIGHT", + argumentName: "--providerRight", + fallback: .mapLibre + ) let initCameraPosition = MapCameraPosition( position: GeoPoint(latitude: 35.6812, longitude: 139.7671, altitude: 0), // Tokyo @@ -101,11 +107,6 @@ class CameraSyncTestViewModel: ObservableObject { return rectangles } - func onLeftCameraChange(_ position: MapCameraPosition, rightState: any MapViewStateProtocol) { - // Sync camera from left map to right map - rightState.moveCameraTo(cameraPosition: position, durationMillis: 0) - } - func getProviderName(_ provider: MapProvider) -> String { switch provider { case .googleMaps: @@ -116,6 +117,18 @@ class CameraSyncTestViewModel: ObservableObject { return "MapKit" case .mapbox: return "Mapbox" + case .arcGIS: + return "ArcGIS" + case .arcGIS2D: + return "ArcGIS 2D" + case .here: + return "Here" + case .tomTom: + return "TomTom" + case .mapTiler: + return "MapTiler" + case .longdo: + return "Longdo" } } } diff --git a/samples/MapConductorSampleApp/Sources/pages/circle/CircleMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/circle/CircleMapComponent.swift index 2123f15..2748b3a 100644 --- a/samples/MapConductorSampleApp/Sources/pages/circle/CircleMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/circle/CircleMapComponent.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI import UIKit @@ -13,32 +18,92 @@ struct CircleMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState + @ObservedObject var viewModel: CirclePageViewModel - let circleState: CircleState - let centerMarker: MarkerState - let edgeMarker: MarkerState + @State private var labelPosition: CGPoint? var body: some View { - SampleMapView( - provider: $provider, - googleState: googleState, - mapLibreState: mapLibreState, - mapKitState: mapKitState, - mapboxState: mapboxState, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) + ZStack { + SampleMapView( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onCameraMove: { _ in calculateLabelPosition() } + ) { + { () -> MapViewContent in + var content = MapViewContent() + content.circles = [Circle(state: viewModel.circleState)] + content.polylines = [ + Polyline(state: PolylineState( + points: [viewModel.circleCenter, GeoPoint.from(position: viewModel.edgeMarker.position)], + id: "circle-radius-line", + strokeColor: UIColor.white, + strokeWidth: 3.0, + geodesic: true, + )) + ] + content.markers = [ + Marker(state: viewModel.centerMarker), + Marker(state: viewModel.edgeMarker) + ] + return content + }() } - ) { - { () -> MapViewContent in - var content = MapViewContent() - content.circles = [Circle(state: circleState)] - content.markers = [ - Marker(state: centerMarker), - Marker(state: edgeMarker) - ] - return content - }() + + if let pos = labelPosition { + radiusLabel(text: "\(Int(viewModel.circleState.radiusMeters)) m") + .position(x: pos.x, y: pos.y) + } + } + .onChange(of: viewModel.labelUpdateTick) { _ in + calculateLabelPosition() } } + + private func calculateLabelPosition() { + let midPoint = Planar.interpolate( + from: viewModel.centerMarker.position, + to: viewModel.edgeMarker.position, + fraction: 0.5 + ) + labelPosition = activeHolder()?.toScreenOffset(position: midPoint) + } + + private func activeHolder() -> AnyMapViewHolder? { + switch provider { + case .googleMaps: return googleState.getMapViewHolder() + case .mapLibre: return mapLibreState.getMapViewHolder() + case .mapKit: return mapKitState.getMapViewHolder() + case .mapbox: return mapboxState.getMapViewHolder() + case .arcGIS, .arcGIS2D: return arcGISState.getMapViewHolder() + case .here: return hereState.getMapViewHolder() + case .tomTom: return tomTomState.getMapViewHolder() + case .mapTiler: return mapTilerState.getMapViewHolder() + case .longdo: return longdoState.getMapViewHolder() + } + } +} + +private func radiusLabel(text: String) -> some View { + ZStack { + Text(text) + .font(.system(size: 16, weight: .bold)) + .foregroundColor(.white) + .shadow(color: .white, radius: 5) + Text(text) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.black) + } } diff --git a/samples/MapConductorSampleApp/Sources/pages/circle/CircleMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/circle/CircleMapPage.swift index 5f4f227..7c0a476 100644 --- a/samples/MapConductorSampleApp/Sources/pages/circle/CircleMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/circle/CircleMapPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct CircleMapPage: View { @@ -15,6 +20,11 @@ struct CircleMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -39,6 +49,36 @@ struct CircleMapPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -50,9 +90,12 @@ struct CircleMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - circleState: viewModel.circleState, - centerMarker: viewModel.centerMarker, - edgeMarker: viewModel.edgeMarker + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + viewModel: viewModel ) VStack(alignment: .leading, spacing: 12) { diff --git a/samples/MapConductorSampleApp/Sources/pages/circle/CirclePageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/circle/CirclePageViewModel.swift index 5facaad..3576a39 100644 --- a/samples/MapConductorSampleApp/Sources/pages/circle/CirclePageViewModel.swift +++ b/samples/MapConductorSampleApp/Sources/pages/circle/CirclePageViewModel.swift @@ -12,6 +12,8 @@ final class CirclePageViewModel: ObservableObject { @Published var fillOpacity: Double @Published var strokeWidth: Double @Published var message: String + // Incremented on drag or camera move to trigger label position recalculation in the View + @Published var labelUpdateTick: Int = 0 private let colors: [UIColor] private var fillColorIndex = 0 @@ -20,16 +22,17 @@ final class CirclePageViewModel: ObservableObject { let initialFillOpacity = 0.3 let initialStrokeWidth = 3.0 let center = GeoPoint(latitude: 21.382314, longitude: -157.933097) - let edge = CirclePageViewModel.calculatePositionAtDistance( - center: center, - distanceMeters: 1000.0, - bearingDegrees: 90.0 + + let edge = Spherical.computeOffset( + origin: center, + distance: 5000000.0, + heading: 90.0 ) self.circleCenter = center self.initCameraPosition = MapCameraPosition( position: center, - zoom: 12.0, + zoom: 3.0, bearing: 0.0, tilt: 0.0, paddings: nil @@ -59,7 +62,7 @@ final class CirclePageViewModel: ObservableObject { position: edge, id: "edge_marker", icon: DefaultMarkerIcon( - fillColor: UIColor.green, + fillColor: UIColor.orange, strokeColor: UIColor.white, label: "E" ), @@ -68,7 +71,11 @@ final class CirclePageViewModel: ObservableObject { self.circleState = CircleState( center: center, - radiusMeters: CirclePageViewModel.computeDistanceBetween(center, edge), + radiusMeters: Spherical.computeDistanceBetween( + from: center, + to: edge + ), + geodesic: true, strokeColor: UIColor.blue.withAlphaComponent(0.5), strokeWidth: initialStrokeWidth, fillColor: UIColor.blue.withAlphaComponent(initialFillOpacity), @@ -103,6 +110,11 @@ final class CirclePageViewModel: ObservableObject { func onMarkerMove(_ dragged: MarkerState) { edgeMarker.position = dragged.position circleState.radiusMeters = radiusMeters + labelUpdateTick += 1 + } + + func onCameraMove(_ camera: MapCameraPosition) { + labelUpdateTick += 1 } func updateCircleFillColor() { @@ -114,55 +126,6 @@ final class CirclePageViewModel: ObservableObject { } private var radiusMeters: Double { - CirclePageViewModel.computeDistanceBetween(circleCenter, edgeMarker.position) - } - - private static func computeDistanceBetween(_ from: GeoPointProtocol, _ to: GeoPointProtocol) -> Double { - let radius = 6_371_009.0 - let lat1 = degreesToRadians(from.latitude) - let lat2 = degreesToRadians(to.latitude) - let deltaLat = lat2 - lat1 - let deltaLng = degreesToRadians(to.longitude - from.longitude) - - let a = sin(deltaLat / 2) * sin(deltaLat / 2) - + cos(lat1) * cos(lat2) * sin(deltaLng / 2) * sin(deltaLng / 2) - let c = 2 * atan2(sqrt(a), sqrt(1 - a)) - return radius * c + Spherical.computeDistanceBetween(from: circleCenter, to: edgeMarker.position) } - - private static func calculatePositionAtDistance( - center: GeoPointProtocol, - distanceMeters: Double, - bearingDegrees: Double - ) -> GeoPoint { - let radius = 6_371_009.0 - let bearing = degreesToRadians(bearingDegrees) - let angularDistance = distanceMeters / radius - let lat1 = degreesToRadians(center.latitude) - let lon1 = degreesToRadians(center.longitude) - - let sinLat1 = sin(lat1) - let cosLat1 = cos(lat1) - let sinAngular = sin(angularDistance) - let cosAngular = cos(angularDistance) - - let lat2 = asin(sinLat1 * cosAngular + cosLat1 * sinAngular * cos(bearing)) - let lon2 = lon1 + atan2( - sin(bearing) * sinAngular * cosLat1, - cosAngular - sinLat1 * sin(lat2) - ) - - let latitude = radiansToDegrees(lat2) - let longitude = radiansToDegrees(lon2) - let wrapped = GeoPoint(latitude: latitude, longitude: longitude).wrap() - return GeoPoint.from(position: wrapped) - } -} - -private func degreesToRadians(_ degrees: Double) -> Double { - degrees * .pi / 180.0 -} - -private func radiansToDegrees(_ radians: Double) -> Double { - radians * 180.0 / .pi } diff --git a/samples/MapConductorSampleApp/Sources/pages/geojson/basic/BasicGeoJSONMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/geojson/basic/BasicGeoJSONMapPage.swift new file mode 100644 index 0000000..8340d73 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/geojson/basic/BasicGeoJSONMapPage.swift @@ -0,0 +1,175 @@ +import MapConductorCore +import MapConductorForLongdo +import MapConductorForArcGIS +import MapConductorForGoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForMapKit +import MapConductorForMapLibre +import MapConductorForMapbox +import MapConductorGeoJSON +import SwiftUI +import UIKit + +struct BasicGeoJSONMapPage: View { + let onToggleSidebar: () -> Void + + @State private var provider: MapProvider + @StateObject private var layerState: GeoJSONLayerState + + @StateObject private var googleState: GoogleMapViewState + @StateObject private var mapLibreState: MapLibreViewState + @StateObject private var mapKitState: MapKitViewState + @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState + + init(onToggleSidebar: @escaping () -> Void = {}) { + self.onToggleSidebar = onToggleSidebar + let cameraPosition = MapCameraPosition( + position: GeoPoint.fromLongLat(longitude: 55.3089185, latitude: 25.255377), + zoom: 13.0 + ) + let style = GeoJSONTileRenderer.LayerStyle( + strokeColor: UIColor(red: 0x1d / 255.0, green: 0x70 / 255.0, blue: 0x82 / 255.0, alpha: 1.0), + fillColor: UIColor(red: 0x3b / 255.0, green: 0xb2 / 255.0, blue: 0xd0 / 255.0, alpha: 1.0), + strokeWidth: 2, + pointRadius: 8 + ) + + _provider = State(initialValue: .mapLibre) + _layerState = StateObject(wrappedValue: GeoJSONLayerState(layerStyle: style)) + _googleState = StateObject(wrappedValue: GoogleMapViewState(cameraPosition: cameraPosition)) + _mapLibreState = StateObject( + wrappedValue: MapLibreViewState( + mapDesignType: MapLibreDesign.DemoTiles, + cameraPosition: cameraPosition + ) + ) + _mapKitState = StateObject( + wrappedValue: MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, + cameraPosition: cameraPosition + ) + ) + _mapboxState = StateObject(wrappedValue: MapboxViewState(cameraPosition: cameraPosition)) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: cameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: cameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: cameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: cameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: cameraPosition + ) + ) + } + + var body: some View { + DemoMapPageScaffold(provider: $provider, onToggleSidebar: onToggleSidebar) { + ZStack(alignment: .bottomLeading) { + SampleMapView( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + ) { + GeoJSONLayer(state: layerState, features: basicGeoJSONFeatures) + } + + VStack(alignment: .leading, spacing: 8) { + Text("GeoJSON") + .font(.headline) + .foregroundColor(.primary) + + Text("Tile-rendered GeoJSON polygon layer.") + .font(.subheadline) + .foregroundColor(.secondary) + } + .padding(16) + .background(Color(UIColor.systemBackground).opacity(0.95)) + .cornerRadius(12) + .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) + .padding(.leading, 16) + .padding(.bottom, 16) + } + } + } +} + +private let basicGeoJSONFeatures: [GeoJSONFeature] = { + guard let data = basicGeoJSONString.data(using: .utf8) else { return [] } + return GeoJSONParser.parse(data: data) +}() + +private let basicGeoJSONString = """ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [55.30122473231012, 25.26476622289597], + [55.29743486255916, 25.25827212207261], + [55.28978863411328, 25.251356725509737], + [55.300027931336984, 25.246425506635504], + [55.307474692951274, 25.244200378933655], + [55.31212891895635, 25.256408010450187], + [55.30774064871093, 25.26266169122738], + [55.301357710197806, 25.264946609615492], + [55.30122473231012, 25.26476622289597] + ], + [ + [55.30084858315658, 25.256531695820797], + [55.298280197635705, 25.252243254705405], + [55.30163885563897, 25.250501032248863], + [55.304059065092645, 25.254700192612702], + [55.30084858315658, 25.256531695820797] + ], + [ + [55.30173763969924, 25.262517391695198], + [55.301095543307355, 25.26122200491396], + [55.30396028103232, 25.259479911263526], + [55.30489872958182, 25.261132667394975], + [55.30173763969924, 25.262517391695198] + ] + ] + }, + "properties": {} + } + ] +} +""" diff --git a/samples/MapConductorSampleApp/Sources/pages/geojson/layer/ExampleGeoJSONLayerLoader.swift b/samples/MapConductorSampleApp/Sources/pages/geojson/layer/ExampleGeoJSONLayerLoader.swift new file mode 100644 index 0000000..ec65ab1 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/geojson/layer/ExampleGeoJSONLayerLoader.swift @@ -0,0 +1,122 @@ +import Foundation +import MapConductorGeoJSON +import UIKit +import ZIPFoundation + +struct ExampleGeoJSONLayerData { + let features: [GeoJSONFeature] + let styleProvider: ExampleGeoJSONStyler +} + +final class ExampleGeoJSONLayerLoader { + private let bundle: Bundle + + init(bundle: Bundle = .main) { + self.bundle = bundle + } + + func load(assetName: String) throws -> ExampleGeoJSONLayerData { + guard let url = bundle.url(forResource: assetName, withExtension: "zip") else { + throw LoaderError.assetNotFound("\(assetName).zip") + } + let archive = try Archive(url: url, accessMode: .read, pathEncoding: nil) + var features: [GeoJSONFeature]? + var styleData: Data? + + for entry in archive where entry.type == .file { + switch entry.path.split(separator: "/").last?.lowercased() { + case Self.geoJSONEntry.lowercased(): + features = GeoJSONParser.parse(data: try extract(entry, from: archive)) + case Self.styleEntry.lowercased(): + styleData = try extract(entry, from: archive) + default: + continue + } + } + + guard let features else { + throw LoaderError.entryNotFound(Self.geoJSONEntry, assetName) + } + guard let styleData else { + throw LoaderError.entryNotFound(Self.styleEntry, assetName) + } + return ExampleGeoJSONLayerData( + features: features, + styleProvider: ExampleGeoJSONStyler(routeColors: try parseRouteColors(styleData)) + ) + } + + private func extract(_ entry: Entry, from archive: Archive) throws -> Data { + var data = Data() + _ = try archive.extract(entry) { data.append($0) } + return data + } + + private func parseRouteColors(_ data: Data) throws -> [ExampleGeoJSONStyler.RouteKey: UIColor] { + guard let companies = try JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { + throw LoaderError.invalidStyleJSON + } + var result: [ExampleGeoJSONStyler.RouteKey: UIColor] = [:] + for item in companies { + guard let company = item["company"] as? [String: Any], + let companyName = nonEmptyString(company["name"]), + let lines = company["lines"] as? [[String: Any]] else { + continue + } + for line in lines { + guard let lineName = nonEmptyString(line["name"]), + let colorString = nonEmptyString(line["color"]), + let color = UIColor(androidHex: colorString) else { + continue + } + result[.init(companyName: companyName, lineName: lineName)] = color + } + } + return result + } + + private func nonEmptyString(_ value: Any?) -> String? { + guard let value = value as? String, !value.isEmpty else { return nil } + return value + } + + private enum LoaderError: LocalizedError { + case assetNotFound(String) + case entryNotFound(String, String) + case invalidStyleJSON + + var errorDescription: String? { + switch self { + case .assetNotFound(let name): + return "\(name) was not found in the app bundle" + case .entryNotFound(let entry, let asset): + return "\(entry) was not found in \(asset)" + case .invalidStyleJSON: + return "The GeoJSON style file does not contain a JSON array" + } + } + } + + private static let geoJSONEntry = "N02-22_RailroadSection.geojson" + private static let styleEntry = "N02-22_RailroadSection.style.json" +} + +private extension UIColor { + convenience init?(androidHex value: String) { + let hex = value.hasPrefix("#") ? String(value.dropFirst()) : value + guard hex.count == 6 || hex.count == 8, + let parsed = UInt64(hex, radix: 16) else { + return nil + } + let alpha: UInt64 = hex.count == 8 ? (parsed >> 24) & 0xff : 0xff + let red = (parsed >> 16) & 0xff + let green = (parsed >> 8) & 0xff + let blue = parsed & 0xff + self.init( + red: CGFloat(red) / 255, + green: CGFloat(green) / 255, + blue: CGFloat(blue) / 255, + alpha: CGFloat(alpha) / 255 + ) + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/geojson/layer/ExampleGeoJSONStyler.swift b/samples/MapConductorSampleApp/Sources/pages/geojson/layer/ExampleGeoJSONStyler.swift new file mode 100644 index 0000000..44e36e2 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/geojson/layer/ExampleGeoJSONStyler.swift @@ -0,0 +1,39 @@ +import MapConductorGeoJSON +import UIKit + +final class ExampleGeoJSONStyler: GeoJSONStyleProvider { + struct RouteKey: Hashable { + let companyName: String + let lineName: String + } + + private let routeColors: [RouteKey: UIColor] + + init(routeColors: [RouteKey: UIColor]) { + self.routeColors = routeColors + } + + func style( + for feature: GeoJSONFeature, + defaultStyle: GeoJSONTileRenderer.LayerStyle + ) -> GeoJSONTileRenderer.LayerStyle { + let baseStyle = DefaultGeoJSONStyleProvider.shared.style( + for: feature, + defaultStyle: defaultStyle + ) + guard let companyName = feature.properties[Self.companyProperty] as? String, + let lineName = feature.properties[Self.lineProperty] as? String, + let color = routeColors[RouteKey(companyName: companyName, lineName: lineName)] else { + return baseStyle + } + return GeoJSONTileRenderer.LayerStyle( + strokeColor: color, + fillColor: baseStyle.fillColor, + strokeWidth: baseStyle.strokeWidth, + pointRadius: baseStyle.pointRadius + ) + } + + private static let companyProperty = "N02_004" + private static let lineProperty = "N02_003" +} diff --git a/samples/MapConductorSampleApp/Sources/pages/geojson/layer/GeoJSONLayerMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/geojson/layer/GeoJSONLayerMapPage.swift new file mode 100644 index 0000000..73e68cd --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/geojson/layer/GeoJSONLayerMapPage.swift @@ -0,0 +1,265 @@ +import MapConductorCore +import MapConductorForArcGIS +import MapConductorForGoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForMapKit +import MapConductorForMapLibre +import MapConductorForMapbox +import MapConductorForLongdo +import MapConductorGeoJSON +import SwiftUI +import UIKit + +struct GeoJSONLayerMapPage: View { + let onToggleSidebar: () -> Void + + @State private var provider: MapProvider + @StateObject private var layerState: GeoJSONLayerState + + @StateObject private var googleState: GoogleMapViewState + @StateObject private var mapLibreState: MapLibreViewState + @StateObject private var mapKitState: MapKitViewState + @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState + + @State private var features: [GeoJSONFeature] = [] + @State private var selectedFeature: GeoJSONFeature? + @State private var tappedPosition: GeoPoint? + @State private var isDataLoading = true + + init(onToggleSidebar: @escaping () -> Void = {}) { + self.onToggleSidebar = onToggleSidebar + let cameraPosition = MapCameraPosition( + position: GeoPoint(latitude: 35.68, longitude: 139.77), + zoom: 13.0 + ) + let style = GeoJSONTileRenderer.LayerStyle( + strokeColor: UIColor(red: 250.0 / 255.0, green: 36.0 / 255.0, blue: 29.0 / 255.0, alpha: 0.5), + fillColor: UIColor(red: 250.0 / 255.0, green: 36.0 / 255.0, blue: 29.0 / 255.0, alpha: 0.0), + strokeWidth: 6, + pointRadius: 8 + ) + + _provider = State(initialValue: .mapLibre) + _layerState = StateObject(wrappedValue: GeoJSONLayerState(layerStyle: style)) + _googleState = StateObject(wrappedValue: GoogleMapViewState(cameraPosition: cameraPosition)) + _mapLibreState = StateObject( + wrappedValue: MapLibreViewState( + mapDesignType: MapLibreDesign.DemoTiles, + cameraPosition: cameraPosition + ) + ) + _mapKitState = StateObject( + wrappedValue: MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, + cameraPosition: cameraPosition + ) + ) + _mapboxState = StateObject(wrappedValue: MapboxViewState(cameraPosition: cameraPosition)) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: cameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: cameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: cameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: cameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: cameraPosition + ) + ) + } + + var body: some View { + DemoMapPageScaffold(provider: $provider, onToggleSidebar: onToggleSidebar) { + ZStack(alignment: .bottomLeading) { + SampleMapView( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onMapClick: handleMapClick + ) { + GeoJSONLayer(state: layerState, features: features) + + if let tappedPosition, let selectedFeature { + InfoBubble( + position: tappedPosition, + contentPadding: 12, + cornerRadius: 6, + tailSize: 10 + ) { + PropertyTable(properties: selectedFeature.properties) + } + } + } + + VStack(alignment: .leading, spacing: 8) { + Text("GeoJSON Layer") + .font(.headline) + .foregroundColor(.primary) + + Text("Tap a feature to inspect its properties.") + .font(.subheadline) + .foregroundColor(.secondary) + } + .padding(16) + .background(Color(UIColor.systemBackground).opacity(0.95)) + .cornerRadius(12) + .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) + .padding(.leading, 16) + .padding(.bottom, 16) + + if isDataLoading { + LoadingOverlay(message: "Parsing \(geoJSONAssetName).zip...") + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + } + } + } + .onAppear { + layerState.onClick = { feature, position in + DispatchQueue.main.async { + selectedFeature = feature + tappedPosition = position + } + } + loadFeaturesIfNeeded() + } + } + + private func handleMapClick(_ geoPoint: GeoPoint) { + selectedFeature = nil + tappedPosition = nil + layerState.processClick(geoPoint: geoPoint, pixelTolerance: 10, zoom: googleState.cameraPosition.zoom) + } + + private func loadFeaturesIfNeeded() { + guard features.isEmpty else { return } + isDataLoading = true + Task { + let loadedData = await loadGeoJSONLayerData() + await MainActor.run { + if let loadedData { + layerState.styleProvider = loadedData.styleProvider + features = loadedData.features + } + isDataLoading = false + } + } + } +} + +private struct PropertyTable: View { + let properties: [String: Any] + + var body: some View { + ScrollView { + VStack(spacing: 0) { + PropertyRow(name: "Property", value: "Value", isHeader: true) + + ForEach(properties.keys.sorted(), id: \.self) { key in + PropertyRow(name: key, value: formatPropertyValue(properties[key]), isHeader: false) + } + } + } + .frame(width: 320) + .frame(maxHeight: 300) + } +} + +private struct PropertyRow: View { + let name: String + let value: String + let isHeader: Bool + + var body: some View { + HStack(spacing: 0) { + cell(name, width: 120) + cell(value, width: 200) + } + .background(isHeader ? Color(UIColor.systemGray5) : Color.clear) + } + + private func cell(_ text: String, width: CGFloat) -> some View { + Text(text) + .font(.caption) + .foregroundColor(.black) + .padding(.horizontal, 10) + .padding(.vertical, 8) + .frame(width: width, alignment: .leading) + .frame(minHeight: 34, alignment: .leading) + .border(Color.gray, width: 1) + } +} + +private struct LoadingOverlay: View { + let message: String + + var body: some View { + VStack(spacing: 12) { + ProgressView() + Text("Loading GeoJSON") + .font(.headline) + Text(message) + .font(.subheadline) + .foregroundColor(.secondary) + } + .padding(24) + .background(Color(UIColor.systemBackground)) + .cornerRadius(12) + .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) + } +} + +private func loadGeoJSONLayerData() async -> ExampleGeoJSONLayerData? { + await Task.detached(priority: .userInitiated) { + do { + return try ExampleGeoJSONLayerLoader().load(assetName: geoJSONAssetName) + } catch { + print("[GeoJSONLayerMapPage] Error loading \(geoJSONAssetName).zip: \(error)") + return nil + } + }.value +} + +private func formatPropertyValue(_ value: Any?) -> String { + guard let value else { return "" } + if JSONSerialization.isValidJSONObject(value), + let data = try? JSONSerialization.data(withJSONObject: value), + let json = String(data: data, encoding: .utf8) { + return json + } + return String(describing: value) +} + +private let geoJSONAssetName = "N02-22_GML" diff --git a/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImageMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImageMapComponent.swift index deb1981..8a7dfcf 100644 --- a/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImageMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImageMapComponent.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct GroundImageMapComponent: View { @@ -12,6 +17,11 @@ struct GroundImageMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState @ObservedObject var viewModel: GroundImagePageViewModel var body: some View { @@ -21,10 +31,11 @@ struct GroundImageMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImageMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImageMapPage.swift index abe48f1..8bfe6cd 100644 --- a/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImageMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImageMapPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI import UIKit @@ -16,6 +21,11 @@ struct GroundImageMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -40,6 +50,26 @@ struct GroundImageMapPage: View { _mapboxState = StateObject(wrappedValue: MapboxViewState( cameraPosition: vm.initCameraPosition )) + _arcGISState = StateObject(wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + )) + _hereState = StateObject(wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + )) + _tomTomState = StateObject(wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + )) + _mapTilerState = StateObject(wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + )) + _longdoState = StateObject(wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + )) } var body: some View { @@ -51,6 +81,11 @@ struct GroundImageMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, viewModel: viewModel ) @@ -76,6 +111,30 @@ struct GroundImageMapPage: View { in: 0.0...1.0 ) } +// +// VStack(alignment: .leading, spacing: 6) { +// Text("tilt: \(String(format: "%.2f", viewModel.tilt))") +// .font(.subheadline) +// .foregroundColor(.primary) +// +// TiltCameraDiagram(tilt: viewModel.tilt) +// .frame(height: 120) +// +// Slider( +// value: Binding( +// get: { viewModel.tilt }, +// set: { newValue in +// viewModel.setTilt(newValue) +// let current = activeState.cameraPosition +// activeState.moveCameraTo( +// cameraPosition: current.copy(tilt: viewModel.tilt), +// durationMillis: 0 +// ) +// } +// ), +// in: -90.0...90.0 +// ) +// } if let message = viewModel.message { Text(message) @@ -93,6 +152,29 @@ struct GroundImageMapPage: View { } } + private var activeState: any MapViewStateProtocol { + switch provider { + case .googleMaps: + return googleState + case .mapLibre: + return mapLibreState + case .mapKit: + return mapKitState + case .mapbox: + return mapboxState + case .arcGIS, .arcGIS2D: + return arcGISState + case .here: + return hereState + case .tomTom: + return tomTomState + case .mapTiler: + return mapTilerState + case .longdo: + return longdoState + } + } + private static func loadImage(named nameOrFilename: String) -> UIImage? { let nsFilename = nameOrFilename as NSString let base = nsFilename.deletingPathExtension @@ -110,3 +192,86 @@ struct GroundImageMapPage: View { return nil } } + +private struct TiltCameraDiagram: View { + let tilt: Double + + var body: some View { + Canvas { context, size in + let width = size.width + let height = size.height + let groundY = height * 0.78 + let originX = width * 0.38 + let originY = groundY + let baseCameraY = height * 0.22 + let tiltAbs = min(abs(tilt), 90.0) + let tiltRad = tiltAbs * .pi / 180.0 + let altitude = groundY - baseCameraY + let targetDistance = min(altitude * tan(tiltRad), width * 0.44) + let targetX = tilt < 0.0 ? originX + targetDistance : originX + let targetY = groundY + let cameraX = tilt < 0.0 ? originX : originX - targetDistance + let cameraY = baseCameraY + let sightEndX = tilt == 0.0 ? cameraX : targetX + let sightEndY = targetY + + context.stroke( + Path { path in + path.move(to: CGPoint(x: width * 0.08, y: groundY)) + path.addLine(to: CGPoint(x: width * 0.94, y: groundY)) + }, + with: .color(Color(UIColor.systemGray4)), + style: StrokeStyle(lineWidth: 3, lineCap: .round) + ) + + context.stroke( + Path { path in + path.move(to: CGPoint(x: cameraX, y: cameraY)) + path.addLine(to: CGPoint(x: cameraX, y: groundY)) + }, + with: .color(Color(UIColor.systemGray2)), + lineWidth: 2 + ) + + context.stroke( + Path { path in + path.move(to: CGPoint(x: cameraX, y: cameraY)) + path.addLine(to: CGPoint(x: sightEndX, y: sightEndY)) + }, + with: .color(Color(red: 1.0, green: 0.78, blue: 0.34)), + style: StrokeStyle(lineWidth: 4, lineCap: .round) + ) + + context.fill( + Path(ellipseIn: CGRect(x: cameraX - 8, y: cameraY - 8, width: 16, height: 16)), + with: .color(Color(red: 0.36, green: 0.65, blue: 1.0)) + ) + context.fill( + Path(ellipseIn: CGRect(x: targetX - 7, y: targetY - 7, width: 14, height: 14)), + with: .color(Color(red: 1.0, green: 0.38, blue: 0.35)) + ) + + var cameraBody = Path() + cameraBody.move(to: CGPoint(x: cameraX - 12, y: cameraY - 8)) + cameraBody.addLine(to: CGPoint(x: cameraX + 14, y: cameraY - 4)) + cameraBody.addLine(to: CGPoint(x: cameraX + 10, y: cameraY + 10)) + cameraBody.addLine(to: CGPoint(x: cameraX - 12, y: cameraY + 8)) + cameraBody.closeSubpath() + context.fill(cameraBody, with: .color(Color(UIColor.secondarySystemBackground))) + context.stroke(cameraBody, with: .color(.white.opacity(0.7)), lineWidth: 1.5) + + context.fill( + Path(ellipseIn: CGRect(x: originX - 3.5, y: originY - 3.5, width: 7, height: 7)), + with: .color(Color(UIColor.systemGray2)) + ) + context.stroke( + Path { path in + path.move(to: CGPoint(x: min(cameraX, targetX), y: groundY + 12)) + path.addLine(to: CGPoint(x: max(cameraX, targetX), y: groundY + 12)) + }, + with: .color(Color(UIColor.systemGray3)), + style: StrokeStyle(lineWidth: 2, lineCap: .round) + ) + } + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImagePageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImagePageViewModel.swift index dac2b06..16bb9e5 100644 --- a/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImagePageViewModel.swift +++ b/samples/MapConductorSampleApp/Sources/pages/groundimage/GroundImagePageViewModel.swift @@ -10,6 +10,7 @@ final class GroundImagePageViewModel: ObservableObject { let markers: [MarkerState] @Published var opacity: Double + @Published var tilt: Double @Published var message: String? = nil private let resources: GroundImageResources @@ -33,6 +34,7 @@ final class GroundImagePageViewModel: ObservableObject { let initialOpacity = 0.5 self.opacity = initialOpacity + self.tilt = initCameraPosition.tilt let bounds = GroundImagePageViewModel.makeBounds(southWest: southWest, northEast: northEast) self.groundImageState = GroundImageState( @@ -99,6 +101,10 @@ final class GroundImagePageViewModel: ObservableObject { groundImageState.opacity = resolved } + func setTilt(_ value: Double) { + tilt = min(max(value, -90.0), 90.0) + } + private func onGroundImageClick(_ event: GroundImageEvent) { if groundImageState.image === resources.image { groundImageState.image = resources.clickedImage diff --git a/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapMapComponent.swift index e974d76..1f3f012 100644 --- a/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapMapComponent.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForLongdo import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import MapConductorHeatmap import SwiftUI import UIKit @@ -14,6 +19,11 @@ struct HeatmapMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let heatmap: HeatmapOverlayState let points: [HeatmapPointState] @@ -26,19 +36,18 @@ struct HeatmapMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, onCameraMove: nil, onCameraMoveEnd: { camera in onCameraMove(provider, camera) - }, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) } ) { HeatmapOverlay(state: heatmap) { - ForEach(points, id: \.id) { pointState in - HeatmapPointView(state: pointState) - } + HeatmapPoints(points) } } } diff --git a/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapMapPage.swift index dfbb29c..b24ae2f 100644 --- a/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapMapPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import MapConductorHeatmap import SwiftUI @@ -16,6 +21,11 @@ struct HeatmapMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -39,6 +49,36 @@ struct HeatmapMapPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -50,6 +90,11 @@ struct HeatmapMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, heatmap: viewModel.heatmap, points: viewModel.heatmapPoints, onCameraMove: viewModel.onCameraMove(provider:camera:) @@ -72,11 +117,11 @@ struct HeatmapMapPage: View { .padding(.bottom, 16) } } - .onAppear { - viewModel.setUseCameraZoomForTiles(isGoogleMaps: provider == .googleMaps) - } - .onChange(of: provider) { next in - viewModel.setUseCameraZoomForTiles(isGoogleMaps: next == .googleMaps) - } +// .onAppear { +// viewModel.setUseCameraZoomForTiles(isGoogleMaps: provider == .googleMaps) +// } +// .onChange(of: provider) { next in +// viewModel.setUseCameraZoomForTiles(isGoogleMaps: next == .googleMaps) +// } } } diff --git a/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapPageViewModel.swift index 6f58a4e..4d69489 100644 --- a/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapPageViewModel.swift +++ b/samples/MapConductorSampleApp/Sources/pages/heatmap/HeatmapPageViewModel.swift @@ -29,9 +29,9 @@ final class HeatmapPageViewModel: ObservableObject { } } - func setUseCameraZoomForTiles(isGoogleMaps: Bool) { - heatmap.useCameraZoomForTiles = true - } +// func setUseCameraZoomForTiles(isGoogleMaps: Bool) { +// heatmap.useCameraZoomForTiles = true +// } func onCameraMove(provider: MapProvider, camera: MapCameraPosition) { heatmap.onCameraChanged(camera) diff --git a/samples/MapConductorSampleApp/Sources/pages/hittest/HereHitTestPage.swift b/samples/MapConductorSampleApp/Sources/pages/hittest/HereHitTestPage.swift new file mode 100644 index 0000000..77946b7 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/hittest/HereHitTestPage.swift @@ -0,0 +1,94 @@ +import MapConductorCore +import MapConductorForHERE +import SwiftUI +import heresdk + +/// Instrumentation page used by `HereHitTestUITests` to measure the HERE marker +/// hit-test box on a real device. +/// +/// A single default-icon marker sits at a fixed position with a fixed camera. The +/// page publishes, via accessibility labels: +/// +/// - `hitTestAnchor` the marker's anchor point in **window** coordinates (points) +/// - `hitTestResult` `":HIT"` / `":MISS"`, bumped once per tap +/// - `hitTestScale` the map view's `pixelScale` +/// +/// A tap that lands inside the hit box reaches the marker's `onClick`; anything else +/// falls through to `onMapClick`. The sequence number lets the test wait for the +/// result of a specific tap rather than racing the previous one. +struct HereHitTestPage: View { + static let markerPosition = GeoPoint(latitude: 35.681236, longitude: 139.767125) + + let onToggleSidebar: () -> Void + + @StateObject private var state = HereMapViewState( + cameraPosition: MapCameraPosition(position: HereHitTestPage.markerPosition, zoom: 15.0) + ) + + @State private var seq: Int = 0 + @State private var result: String = "NONE" + @State private var anchor: String = "?" + @State private var scale: String = "?" + @State private var mapFrame: CGRect = .zero + + private let refresh = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect() + + var body: some View { + ZStack(alignment: .top) { + GeometryReader { geo in + if SampleConfig.hereAccessKeyId != nil { + HereMapView( + state: state, + onMapClick: { _ in record("MISS") }, + sdkInitialize: SampleMapView.initializeAllSDKs + ) { + Marker(state: MarkerState( + position: HereHitTestPage.markerPosition, + id: "hittest_target", + icon: DefaultMarkerIcon(), + onClick: { _ in record("HIT") } + )) + } + .onAppear { mapFrame = geo.frame(in: .global) } + .onChange(of: geo.frame(in: .global)) { _, newFrame in mapFrame = newFrame } + } else { + Text("Here is not available due to no api key") + } + } + + // Keep the readouts out of the probe area; the test only taps near center. + VStack(spacing: 0) { + Text(anchor).accessibilityIdentifier("hitTestAnchor") + Text(result).accessibilityIdentifier("hitTestResult") + Text(scale).accessibilityIdentifier("hitTestScale") + } + .font(.caption2) + .opacity(0.6) + } + .onReceive(refresh) { _ in updateAnchor() } + } + + private func record(_ outcome: String) { + seq += 1 + result = "\(seq):\(outcome)" + } + + /// Projects the marker to the map view's local space and lifts it into window + /// coordinates, which is the space `XCUICoordinate` taps are expressed in. + private func updateAnchor() { + guard let holder = state.getMapViewHolder(), + let local = holder.toScreenOffset(position: HereHitTestPage.markerPosition) + else { + anchor = "?" + return + } + anchor = String( + format: "%.2f,%.2f", + mapFrame.origin.x + local.x, + mapFrame.origin.y + local.y + ) + if let native = holder.mapView as? MapView { + scale = String(format: "%.2f", native.pixelScale) + } + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/infobubble/MultipleBubblesPage.swift b/samples/MapConductorSampleApp/Sources/pages/infobubble/MultipleBubblesPage.swift index 04e758e..d659bb9 100644 --- a/samples/MapConductorSampleApp/Sources/pages/infobubble/MultipleBubblesPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/infobubble/MultipleBubblesPage.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForLongdo import SwiftUI import UIKit @@ -47,6 +52,43 @@ struct MultipleBubblesPage: View { ) ) + @StateObject private var arcGISState = ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 15 + ) + ) + + @StateObject private var hereState = HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 15 + ) + ) + @StateObject private var tomTomState = TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 15 + ) + ) + @StateObject private var mapTilerState = MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 15 + ) + ) + @StateObject private var longdoState = LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 15 + ) + ) + private let markerState1 = MarkerState( position: GeoPoint(latitude: 37.7749, longitude: -122.4194), id: "marker_1", @@ -76,12 +118,13 @@ struct MultipleBubblesPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, onMapClick: { _ in selectedMarkers = [] - }, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) } ) { Marker(state: markerState1) diff --git a/samples/MapConductorSampleApp/Sources/pages/infobubble/RichContentBubblePage.swift b/samples/MapConductorSampleApp/Sources/pages/infobubble/RichContentBubblePage.swift index bca5f5a..930d0a1 100644 --- a/samples/MapConductorSampleApp/Sources/pages/infobubble/RichContentBubblePage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/infobubble/RichContentBubblePage.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForLongdo import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import SwiftUI import UIKit @@ -53,6 +58,43 @@ struct RichContentBubblePage: View { zoom: 10 ) ) + + @StateObject private var arcGISState = ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 10 + ) + ) + + @StateObject private var hereState = HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 10 + ) + ) + @StateObject private var tomTomState = TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 10 + ) + ) + @StateObject private var mapTilerState = MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 10 + ) + ) + @StateObject private var longdoState = LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 10 + ) + ) @StateObject private var markerState = MarkerState( position: GeoPoint(latitude: 37.7694, longitude: -122.4862), @@ -72,17 +114,25 @@ struct RichContentBubblePage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - onMapClick: { _ in selectedMarker = nil }, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onMapClick: { _ in selectedMarker = nil } ) { Marker(state: markerState) if let marker = selectedMarker, let info = marker.extra as? LocationInfo { - InfoBubble(marker: marker, style: bubbleStyle()) { + InfoBubble( + marker: marker, + bubbleColor: colorScheme == .dark ? .black : .white, + borderColor: colorScheme == .dark ? .gray : .black, + contentPadding: 16, + cornerRadius: 12, + tailSize: 10 + ) { VStack(alignment: .leading, spacing: 8) { Text(info.name) .font(.headline) @@ -114,14 +164,4 @@ struct RichContentBubblePage: View { selectedMarker = markerState } } - - private func bubbleStyle() -> InfoBubbleStyle { - InfoBubbleStyle( - bubbleColor: colorScheme == .dark ? .black : .white, - borderColor: colorScheme == .dark ? .gray : .black, - contentPadding: 16, - cornerRadius: 12, - tailSize: 10 - ) - } } diff --git a/samples/MapConductorSampleApp/Sources/pages/infobubble/SimpleTextBubblePage.swift b/samples/MapConductorSampleApp/Sources/pages/infobubble/SimpleTextBubblePage.swift index e1ad734..b215222 100644 --- a/samples/MapConductorSampleApp/Sources/pages/infobubble/SimpleTextBubblePage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/infobubble/SimpleTextBubblePage.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForLongdo import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import SwiftUI import UIKit @@ -46,6 +51,43 @@ struct SimpleTextBubblePage: View { zoom: 10 ) ) + + @StateObject private var arcGISState = ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 10 + ) + ) + + @StateObject private var hereState = HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 10 + ) + ) + @StateObject private var tomTomState = TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 10 + ) + ) + @StateObject private var mapTilerState = MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 10 + ) + ) + @StateObject private var longdoState = LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 37.7749, longitude: -122.4194), + zoom: 10 + ) + ) @StateObject private var markerState = MarkerState( position: GeoPoint(latitude: 37.7749, longitude: -122.4194), @@ -61,21 +103,26 @@ struct SimpleTextBubblePage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - onMapClick: { _ in selectedMarker = nil }, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onMapClick: { _ in selectedMarker = nil } ) { Marker(state: markerState) - if let marker = selectedMarker { - InfoBubble(marker: marker) { - Text(marker.extra as? String ?? "No information") - .foregroundColor(.accentColor) - .padding(4) - } - } + + if let marker = selectedMarker { + InfoBubble(marker: marker) { + Text(marker.extra as! String) + .foregroundColor(.accentColor) + .padding(4) + } + } + + + } } .onAppear { diff --git a/samples/MapConductorSampleApp/Sources/pages/infobubble/StyledInfoBubblePage.swift b/samples/MapConductorSampleApp/Sources/pages/infobubble/StyledInfoBubblePage.swift index 878d9a0..675185d 100644 --- a/samples/MapConductorSampleApp/Sources/pages/infobubble/StyledInfoBubblePage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/infobubble/StyledInfoBubblePage.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct StyledInfoBubblePage: View { @@ -44,19 +49,56 @@ struct StyledInfoBubblePage: View { zoom: 12 ) ) + + @StateObject private var arcGISState = ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 12 + ) + ) + + @StateObject private var hereState = HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 12 + ) + ) + @StateObject private var tomTomState = TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 12 + ) + ) + @StateObject private var mapTilerState = MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 12 + ) + ) + + @StateObject private var longdoState = LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: MapCameraPosition( + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 12 + ) + ) @StateObject private var markerState = MarkerState( position: GeoPoint(latitude: 35.6812, longitude: 139.7671) ) - private let style = InfoBubbleStyle( - bubbleColor: Color.black.opacity(0.85), - borderColor: Color.white, - contentPadding: 10, - cornerRadius: 10, - tailSize: 10 + /// Second marker, used to demo `InfoBubbleCustom` — content drawn entirely by us, + /// tail included. Mirrors the Android example's `InfoBubbleCustom` section. + @StateObject private var customMarkerState = MarkerState( + position: GeoPoint(latitude: 35.6812, longitude: 139.7971) ) + var body: some View { DemoMapPageScaffold(provider: $provider, onToggleSidebar: onToggleSidebar) { SampleMapView( @@ -65,14 +107,22 @@ struct StyledInfoBubblePage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - onMapClick: { point in markerState.position = point }, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onMapClick: { point in markerState.position = point } ) { Marker(state: markerState) - InfoBubble(marker: markerState, style: style) { + InfoBubble( + marker: markerState, + bubbleColor: Color.black.opacity(0.85), + borderColor: Color.white, + contentPadding: 10, + cornerRadius: 10, + tailSize: 10 + ) { VStack(alignment: .leading, spacing: 6) { Text("Night Mode") .font(.headline) @@ -82,7 +132,87 @@ struct StyledInfoBubblePage: View { .foregroundColor(.white.opacity(0.8)) } } + + Marker(state: customMarkerState) + // The tail is part of our own drawing, so tell the overlay engine that + // the connection point sits at the content box's center-left. + InfoBubbleCustom( + marker: customMarkerState, + tailOffset: CGPoint(x: 0, y: 0.5) + ) { + RightTailInfoBubble(bubbleColor: .white, borderColor: .black) { + Text("Fully custom bubble") + .font(.subheadline) + .foregroundColor(.accentColor) + } + } } } } } + +/// A bubble that sits to the right of its marker, with the tail on its own left edge. +/// Ported from the Android example's `RightTailInfoBubble` composable. +private struct RightTailInfoBubble: View { + var bubbleColor: Color = .white + var borderColor: Color = .black + var contentPadding: CGFloat = 8 + var cornerRadius: CGFloat = 4 + var tailSize: CGFloat = 8 + @ViewBuilder var content: Content + + private var shape: RightTailBubbleShape { + RightTailBubbleShape(tail: tailSize, corner: cornerRadius) + } + + var body: some View { + content + .padding(contentPadding) + .padding(.leading, tailSize) + .background(shape.fill(bubbleColor)) + .overlay(shape.stroke(borderColor, lineWidth: 1)) + } +} + +/// Rounded rect occupying `x ∈ [tail, width]`, plus a triangular tail pointing left +/// from the vertical center — so the tail tip lands at the box's `(0, 0.5)`. +private struct RightTailBubbleShape: Shape { + let tail: CGFloat + let corner: CGFloat + + func path(in rect: CGRect) -> Path { + let left = rect.minX + tail + let right = rect.maxX + let top = rect.minY + let bottom = rect.maxY + let midY = rect.midY + + var path = Path() + path.move(to: CGPoint(x: left + corner, y: top)) + path.addArc( + tangent1End: CGPoint(x: right, y: top), + tangent2End: CGPoint(x: right, y: bottom), + radius: corner + ) + path.addArc( + tangent1End: CGPoint(x: right, y: bottom), + tangent2End: CGPoint(x: left, y: bottom), + radius: corner + ) + path.addArc( + tangent1End: CGPoint(x: left, y: bottom), + tangent2End: CGPoint(x: left, y: top), + radius: corner + ) + path.addLine(to: CGPoint(x: left, y: midY + tail / 2)) + path.addLine(to: CGPoint(x: rect.minX, y: midY)) + path.addLine(to: CGPoint(x: left, y: midY - tail / 2)) + path.addArc( + tangent1End: CGPoint(x: left, y: top), + tangent2End: CGPoint(x: right, y: top), + radius: corner + ) + path.closeSubpath() + return path + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/map/SampleMapView.swift b/samples/MapConductorSampleApp/Sources/pages/map/SampleMapView.swift index f04e4e9..2f9b6ff 100644 --- a/samples/MapConductorSampleApp/Sources/pages/map/SampleMapView.swift +++ b/samples/MapConductorSampleApp/Sources/pages/map/SampleMapView.swift @@ -1,9 +1,16 @@ +import Foundation import GoogleMaps import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo +import LongdoMapFramework import SwiftUI enum MapProvider: String, CaseIterable, Identifiable { @@ -11,46 +18,63 @@ enum MapProvider: String, CaseIterable, Identifiable { case mapLibre = "MapLibre" case mapKit = "MapKit" case mapbox = "Mapbox" + case arcGIS = "ArcGIS" + case arcGIS2D = "ArcGIS 2D" + case here = "Here" + case tomTom = "TomTom" + case mapTiler = "MapTiler" + case longdo = "Longdo" var id: String { rawValue } + + static let allCases: [MapProvider] = [ + .googleMaps, .mapLibre, .mapKit, .mapbox, .arcGIS, .arcGIS2D, .here, .tomTom, .mapTiler, .longdo, + ] } extension MapProvider { - static func initial() -> MapProvider { + /// 名前文字列からプロバイダを解決する。`nil` は該当なし。 + static func parse(_ raw: String) -> MapProvider? { + switch raw.lowercased() { + case "maplibre", "map_libre": return .mapLibre + case "googlemaps", "google_maps", "google": return .googleMaps + case "mapkit", "map_kit": return .mapKit + case "mapbox": return .mapbox + case "arcgis2d", "arcgis_2d", "arcgis-2d": return .arcGIS2D + case "arcgis", "arc_gis": return .arcGIS + case "here": return .here + case "tomtom", "tom_tom": return .tomTom + case "maptiler", "map_tiler": return .mapTiler + case "longdo": return .longdo + default: return nil + } + } + + /// 起動時に選択しておくプロバイダ。UI テストが環境変数/起動引数で指定する。 + /// + /// - Parameters: + /// - environmentKey: 参照する環境変数名。ペインが 2 つあるページ(Camera Sync Test)は + /// 右ペイン用に別のキーを渡す。 + /// - argumentName: 参照する起動引数名。 + /// - fallback: どちらも指定されていないときの既定値。 + static func initial( + environmentKey: String = "MAPCONDUCTOR_SAMPLE_PROVIDER", + argumentName: String = "--provider", + fallback: MapProvider = .googleMaps + ) -> MapProvider { let env = ProcessInfo.processInfo.environment - if let value = env["MAPCONDUCTOR_SAMPLE_PROVIDER"]?.lowercased() { - if value == "maplibre" || value == "map_libre" { - return .mapLibre - } - if value == "googlemaps" || value == "google_maps" || value == "google" { - return .googleMaps - } - if value == "mapkit" || value == "map_kit" { - return .mapKit - } - if value == "mapbox" { - return .mapbox - } + if let value = env[environmentKey], let provider = parse(value) { + return provider } let args = ProcessInfo.processInfo.arguments - if let index = args.firstIndex(of: "--provider"), index + 1 < args.count { - let value = args[index + 1].lowercased() - if value == "maplibre" || value == "map_libre" { - return .mapLibre - } - if value == "googlemaps" || value == "google_maps" || value == "google" { - return .googleMaps - } - if value == "mapkit" || value == "map_kit" { - return .mapKit - } - if value == "mapbox" { - return .mapbox - } + if let index = args.firstIndex(of: argumentName), + index + 1 < args.count, + let provider = parse(args[index + 1]) { + return provider } - return .googleMaps + return fallback } } @@ -60,24 +84,59 @@ struct SampleMapView: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState + /// カメラの可動範囲制限。`nil` で無制限。選択中のプロバイダの MapView へそのまま渡す。 + var cameraRestriction: CameraRestriction? = nil var onMapClick: ((GeoPoint) -> Void)? = nil + var onMapLongClick: ((GeoPoint) -> Void)? = nil var onCameraMoveStart: ((MapCameraPosition) -> Void)? = nil var onCameraMove: ((MapCameraPosition) -> Void)? = nil var onCameraMoveEnd: ((MapCameraPosition) -> Void)? = nil - var sdkInitialize: (() -> Void)? = nil + var sdkInitialize: (() -> Void)? let content: () -> MapViewContent + static func initializeAllSDKs() { + if let apiKey = SampleConfig.googleMapsApiKey { + GMSServices.provideAPIKey(apiKey) + } + if let accessToken = SampleConfig.mapboxAccessToken { + initializeMapbox(accessToken: accessToken) + } + if let apiKey = SampleConfig.arcGISApiKey { + _ = arcGISApiKeyInitialize(apiKey: apiKey) + } + if let accessKey = SampleConfig.hereAccessKeyId, + let accessSecret = SampleConfig.hereAccessKeySecret { + do { + try hereKeyInitialize(accessKeyId: accessKey, accessKeySecret: accessSecret) + } catch { + NSLog("[MapConductor] HERE authentication failed: %@", String(describing: error)) + } + } + } + init( provider: Binding, googleState: GoogleMapViewState, mapLibreState: MapLibreViewState, mapKitState: MapKitViewState, mapboxState: MapboxViewState, + arcGISState: ArcGISMapViewState, + hereState: HereMapViewState, + tomTomState: TomTomMapViewState, + mapTilerState: MapTilerViewState, + longdoState: LongdoViewState, + cameraRestriction: CameraRestriction? = nil, onMapClick: ((GeoPoint) -> Void)? = nil, + onMapLongClick: ((GeoPoint) -> Void)? = nil, onCameraMoveStart: ((MapCameraPosition) -> Void)? = nil, onCameraMove: ((MapCameraPosition) -> Void)? = nil, onCameraMoveEnd: ((MapCameraPosition) -> Void)? = nil, - sdkInitialize: (() -> Void)? = nil, + sdkInitialize: (() -> Void)? = SampleMapView.initializeAllSDKs, @MapViewContentBuilder content: @escaping () -> MapViewContent ) { self._provider = provider @@ -85,7 +144,14 @@ struct SampleMapView: View { self.mapLibreState = mapLibreState self.mapKitState = mapKitState self.mapboxState = mapboxState + self.arcGISState = arcGISState + self.hereState = hereState + self.tomTomState = tomTomState + self.mapTilerState = mapTilerState + self.longdoState = longdoState + self.cameraRestriction = cameraRestriction self.onMapClick = onMapClick + self.onMapLongClick = onMapLongClick self.onCameraMoveStart = onCameraMoveStart self.onCameraMove = onCameraMove self.onCameraMoveEnd = onCameraMoveEnd @@ -96,46 +162,182 @@ struct SampleMapView: View { var body: some View { switch provider { case .googleMaps: - GoogleMapView( - state: googleState, - onMapClick: onMapClick, - onCameraMoveStart: onCameraMoveStart, - onCameraMove: onCameraMove, - onCameraMoveEnd: onCameraMoveEnd, - sdkInitialize: sdkInitialize, - content: content - ) - + if let _ = SampleConfig.googleMapsApiKey { + GoogleMapView( + state: googleState, + cameraRestriction: cameraRestriction, + onMapClick: onMapClick, + onMapLongClick: onMapLongClick, + onCameraMoveStart: onCameraMoveStart, + onCameraMove: onCameraMove, + onCameraMoveEnd: onCameraMoveEnd, + sdkInitialize: sdkInitialize, + content: content + ) + } else { + Text("Google Map is not available due to no API key") + } case .mapLibre: MapLibreMapView( state: mapLibreState, + cameraRestriction: cameraRestriction, onMapClick: onMapClick, + onMapLongClick: onMapLongClick, onCameraMoveStart: onCameraMoveStart, onCameraMove: onCameraMove, onCameraMoveEnd: onCameraMoveEnd, + sdkInitialize: sdkInitialize, content: content ) case .mapKit: MapKitMapView( state: mapKitState, + cameraRestriction: cameraRestriction, onMapClick: onMapClick, + onMapLongClick: onMapLongClick, onCameraMoveStart: onCameraMoveStart, onCameraMove: onCameraMove, onCameraMoveEnd: onCameraMoveEnd, + sdkInitialize: sdkInitialize, content: content ) case .mapbox: - MapboxMapView( - state: mapboxState, - onMapClick: onMapClick, - onCameraMoveStart: onCameraMoveStart, - onCameraMove: onCameraMove, - onCameraMoveEnd: onCameraMoveEnd, - sdkInitialize: sdkInitialize, - content: content - ) + if let _ = SampleConfig.mapboxAccessToken { + MapboxMapView( + state: mapboxState, + cameraRestriction: cameraRestriction, + onMapClick: onMapClick, + onMapLongClick: onMapLongClick, + onCameraMoveStart: onCameraMoveStart, + onCameraMove: onCameraMove, + onCameraMoveEnd: onCameraMoveEnd, + sdkInitialize: sdkInitialize, + content: content + ) + } else { + Text("MapBox is not available due to no access token") + } + + + case .arcGIS: + if let _ = SampleConfig.arcGISApiKey { + ArcGISMapView( + state: arcGISState, + cameraRestriction: cameraRestriction, + onMapClick: onMapClick, + onMapLongClick: onMapLongClick, + onCameraMoveStart: onCameraMoveStart, + onCameraMove: onCameraMove, + onCameraMoveEnd: onCameraMoveEnd, + sdkInitialize: sdkInitialize, + content: content + ) + } else { + Text("ArcGIS is not available due to no api key") + } + + + case .arcGIS2D: + if let _ = SampleConfig.arcGISApiKey { + // 2D (MapView) と 3D (SceneView) は同じ ArcGISMapViewState を共有する。 + // プロバイダ切り替えでは同時に描画されないため、状態を分ける必要はない。 + ArcGISMapView2D( + state: arcGISState, + cameraRestriction: cameraRestriction, + onMapClick: onMapClick, + onMapLongClick: onMapLongClick, + onCameraMoveStart: onCameraMoveStart, + onCameraMove: onCameraMove, + onCameraMoveEnd: onCameraMoveEnd, + sdkInitialize: sdkInitialize, + content: content + ) + } else { + Text("ArcGIS is not available due to no api key") + } + + case .here: + if let _ = SampleConfig.hereAccessKeyId, + let _ = SampleConfig.hereAccessKeySecret { + HereMapView( + state: hereState, + cameraRestriction: cameraRestriction, + onMapClick: onMapClick, + onMapLongClick: onMapLongClick, + onCameraMoveStart: onCameraMoveStart, + onCameraMove: onCameraMove, + onCameraMoveEnd: onCameraMoveEnd, + sdkInitialize: sdkInitialize, + content: content + ) + } else { + Text("Here is not available due to no api key") + } + + case .tomTom: + if let _ = SampleConfig.tomTomApiKey { + TomTomMapView( + state: tomTomState, + apiKey: SampleConfig.tomTomApiKey, + cameraRestriction: cameraRestriction, + onMapClick: onMapClick, + onMapLongClick: onMapLongClick, + onCameraMoveStart: onCameraMoveStart, + onCameraMove: onCameraMove, + onCameraMoveEnd: onCameraMoveEnd, + sdkInitialize: sdkInitialize, + content: content + ) + } else { + Text("TomTom is not available due to no api key") + } + + case .mapTiler: + if let _ = SampleConfig.mapTilerApiKey { + MapTilerMapView( + state: mapTilerState, + apiKey: SampleConfig.mapTilerApiKey, + cameraRestriction: cameraRestriction, + onMapClick: onMapClick, + onMapLongClick: onMapLongClick, + onCameraMoveStart: onCameraMoveStart, + onCameraMove: onCameraMove, + onCameraMoveEnd: onCameraMoveEnd, + sdkInitialize: sdkInitialize, + content: content + ) + } else { + Text("MapTiler is not available due to no api key") + } + + case .longdo: + if let _ = SampleConfig.longdoApiKey { + LongdoMapView( + state: longdoState, + apiKey: SampleConfig.longdoApiKey, + cameraRestriction: cameraRestriction, + onMapClick: onMapClick, + onMapLongClick: onMapLongClick, + onCameraMoveStart: onCameraMoveStart, + onCameraMove: onCameraMove, + onCameraMoveEnd: onCameraMoveEnd, + sdkInitialize: { + if let map = longdoState.mapViewHolder?.map { + // 右上のレイヤー切り替えドロップダウンはサンプルアプリの + // provider メニューと重なるため非表示にする + _ = map.call(method: "Ui.LayerSelector.visible", args: [false]) + } + + sdkInitialize?() + }, + content: content + ) + } else { + Text("Longdo is not available due to no api key") + } + } } } diff --git a/samples/MapConductorSampleApp/Sources/pages/map/basic/StoreMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/map/basic/StoreMapComponent.swift index 29bbd6a..30aea83 100644 --- a/samples/MapConductorSampleApp/Sources/pages/map/basic/StoreMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/map/basic/StoreMapComponent.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI import UIKit @@ -13,6 +18,11 @@ struct StoreMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let markers: [MarkerState] let selectedMarker: MarkerState? @@ -27,6 +37,11 @@ struct StoreMapComponent: View { mapLibreState: MapLibreViewState, mapKitState: MapKitViewState, mapboxState: MapboxViewState, + arcGISState: ArcGISMapViewState, + hereState: HereMapViewState, + tomTomState: TomTomMapViewState, + mapTilerState: MapTilerViewState, + longdoState: LongdoViewState, markers: [MarkerState], selectedMarker: MarkerState?, onDirectionButtonClick: @escaping (MarkerState) -> Void, @@ -37,6 +52,11 @@ struct StoreMapComponent: View { self.mapLibreState = mapLibreState self.mapKitState = mapKitState self.mapboxState = mapboxState + self.arcGISState = arcGISState + self.hereState = hereState + self.tomTomState = tomTomState + self.mapTilerState = mapTilerState + self.longdoState = longdoState self.markers = markers self.selectedMarker = selectedMarker self.onDirectionButtonClick = onDirectionButtonClick @@ -51,15 +71,14 @@ struct StoreMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - onMapClick: onMapClick, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onMapClick: onMapClick ) { - for markerState in markerList { - Marker(state: markerState) - } + Markers(markerList) if let marker = selectedMarker, let info = marker.extra as? StoreInfo { InfoBubble(marker: marker) { diff --git a/samples/MapConductorSampleApp/Sources/pages/map/basic/StoreMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/map/basic/StoreMapPage.swift index 5d99266..6a832d0 100644 --- a/samples/MapConductorSampleApp/Sources/pages/map/basic/StoreMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/map/basic/StoreMapPage.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForLongdo import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import SwiftUI struct StoreMapPage: View { @@ -33,6 +38,29 @@ struct StoreMapPage: View { @StateObject private var mapboxState = MapboxViewState( cameraPosition: StoreDemoData.initCameraPosition ) + + @StateObject private var arcGISState = ArcGISMapViewState( + mapDesignType: ArcGISDesign.Streets, + cameraPosition: StoreDemoData.initCameraPosition + ) + + @StateObject private var hereState = HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: StoreDemoData.initCameraPosition + ) + @StateObject private var tomTomState = TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: StoreDemoData.initCameraPosition + ) + @StateObject private var mapTilerState = MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: StoreDemoData.initCameraPosition + ) + + @StateObject private var longdoState = LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: StoreDemoData.initCameraPosition + ) var body: some View { DemoMapPageScaffold(provider: $provider, onToggleSidebar: onToggleSidebar) { @@ -42,6 +70,11 @@ struct StoreMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, markers: viewModel.markerList, selectedMarker: viewModel.selectedMarker, onDirectionButtonClick: { marker in diff --git a/samples/MapConductorSampleApp/Sources/pages/map/camerarestriction/CameraRestrictionPage.swift b/samples/MapConductorSampleApp/Sources/pages/map/camerarestriction/CameraRestrictionPage.swift new file mode 100644 index 0000000..6ff12eb --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/map/camerarestriction/CameraRestrictionPage.swift @@ -0,0 +1,242 @@ +import GoogleMaps +import MapConductorCore +import MapConductorForArcGIS +import MapConductorForGoogleMaps +import MapConductorForHERE +import MapConductorForLongdo +import MapConductorForMapKit +import MapConductorForMapLibre +import MapConductorForMapTiler +import MapConductorForMapbox +import MapConductorForTomTom +import SwiftUI + +/// Exercises `CameraRestriction` against a live map — the iOS counterpart of the +/// example-app's camera-restriction page. +/// +/// The allowed rectangle is drawn as a polygon so the limit is visible, and the +/// buttons ask the camera to go somewhere it is not allowed: +/// +/// - **Move outside** : pans far north-east of the rectangle. +/// - **Zoom over max** / **Zoom under min** : asks for a zoom outside the range. +/// +/// Providers with a native restriction API (Google, Mapbox) refuse the move +/// outright; the clamp-based ones (HERE, ArcGIS, TomTom, MapKit, Longdo, and the +/// pan axis of MapLibre/MapTiler) let it happen and pull the camera back when the +/// gesture settles. Either way the readout must end up inside the limits. +/// +/// The camera is published through the `cameraReadout` accessibility identifier so +/// a device run can assert the clamp actually happened. +struct CameraRestrictionPage: View { + let onToggleSidebar: () -> Void + + init(onToggleSidebar: @escaping () -> Void = {}) { + self.onToggleSidebar = onToggleSidebar + } + + // MARK: - Limits + + /// 許可する矩形(東京駅周辺)。 + private static let south = 35.63 + private static let west = 139.70 + private static let north = 35.75 + private static let east = 139.85 + + private static let minZoom = 12.0 + private static let maxZoom = 16.0 + + private static let allowedBounds = GeoRectBounds( + southWest: GeoPoint(latitude: south, longitude: west), + northEast: GeoPoint(latitude: north, longitude: east) + ) + + static let restriction = CameraRestriction( + bounds: allowedBounds, + minZoom: minZoom, + maxZoom: maxZoom + ) + + private static let startPosition = GeoPoint(latitude: 35.681236, longitude: 139.767125) + private static let start = MapCameraPosition(position: startPosition, zoom: 14) + + /// 制限矩形を可視化するポリゴン。 + private static let boundsPolygon = PolygonState( + points: [ + GeoPoint(latitude: south, longitude: west), + GeoPoint(latitude: south, longitude: east), + GeoPoint(latitude: north, longitude: east), + GeoPoint(latitude: north, longitude: west), + ], + id: "camera-restriction-bounds", + strokeColor: .systemRed, + strokeWidth: 3.0, + fillColor: UIColor.systemRed.withAlphaComponent(0.10), + geodesic: false + ) + + // MARK: - State + + /// UI テストが起動時に制限 ON で開始できるようにする(トグルを座標タップするより確実)。 + private static func initialEnabled() -> Bool { + ProcessInfo.processInfo.environment["MAPCONDUCTOR_SAMPLE_CAMERA_RESTRICTION"] != "off" + } + + @State private var provider: MapProvider = MapProvider.initial() + @State private var enabled = CameraRestrictionPage.initialEnabled() + @State private var cameraText = "?" + + @StateObject private var googleState = GoogleMapViewState(cameraPosition: start) + @StateObject private var mapLibreState = MapLibreViewState( + mapDesignType: MapLibreDesign.DemoTiles, cameraPosition: start) + @StateObject private var mapKitState = MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, cameraPosition: start) + @StateObject private var mapboxState = MapboxViewState(cameraPosition: start) + @StateObject private var arcGISState = ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, cameraPosition: start) + @StateObject private var hereState = HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, cameraPosition: start) + @StateObject private var tomTomState = TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, cameraPosition: start) + @StateObject private var mapTilerState = MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, cameraPosition: start) + @StateObject private var longdoState = LongdoViewState( + mapDesignType: LongdoDesign.Normal, cameraPosition: start) + + var body: some View { + DemoMapPageScaffold(provider: $provider, onToggleSidebar: onToggleSidebar) { + SampleMapView( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + cameraRestriction: enabled ? CameraRestrictionPage.restriction : nil, + onCameraMove: { camera in updateReadout(camera) }, + onCameraMoveEnd: { camera in updateReadout(camera) } + ) { + Polygon(state: CameraRestrictionPage.boundsPolygon) + } + + // 右上のプロバイダピッカーと重ならないよう下端に寄せる。 + VStack { + Spacer() + controlPanel + } + } + } + + private var controlPanel: some View { + VStack(alignment: .leading, spacing: 6) { + Toggle("restriction", isOn: $enabled) + .accessibilityIdentifier("restrictionToggle") + .font(.caption) + .frame(maxWidth: 260) + + HStack(spacing: 8) { + button("Move outside", "moveOutside") { + // 矩形の北東よりさらに外側へ。 + moveCamera( + to: GeoPoint(latitude: 36.20, longitude: 140.40), + zoom: currentZoomOrDefault() + ) + } + button("Zoom > max", "zoomOverMax") { + moveCamera(to: CameraRestrictionPage.startPosition, zoom: 20) + } + button("Zoom < min", "zoomUnderMin") { + moveCamera(to: CameraRestrictionPage.startPosition, zoom: 8) + } + button("Reset", "resetCamera") { + moveCamera( + to: CameraRestrictionPage.startPosition, + zoom: CameraRestrictionPage.start.zoom + ) + } + } + + Text(cameraText) + .font(.caption2) + .accessibilityIdentifier("cameraReadout") + + Text(limitsText) + .font(.caption2) + .foregroundStyle(.secondary) + } + .padding(10) + .background(.thinMaterial) + .cornerRadius(12) + .padding(10) + } + + private var limitsText: String { + String( + format: "limits lat %.2f..%.2f lng %.2f..%.2f zoom %.0f..%.0f", + CameraRestrictionPage.south, + CameraRestrictionPage.north, + CameraRestrictionPage.west, + CameraRestrictionPage.east, + CameraRestrictionPage.minZoom, + CameraRestrictionPage.maxZoom + ) + } + + private func button( + _ title: String, + _ identifier: String, + _ action: @escaping () -> Void + ) -> some View { + Button(title, action: action) + .accessibilityIdentifier(identifier) + .font(.caption) + .buttonStyle(.bordered) + } + + private func updateReadout(_ camera: MapCameraPosition) { + cameraText = String( + format: "%.5f,%.5f,%.2f", + camera.position.latitude, + camera.position.longitude, + camera.zoom + ) + } + + private func currentZoomOrDefault() -> Double { + currentCamera()?.zoom ?? CameraRestrictionPage.start.zoom + } + + private func currentCamera() -> MapCameraPosition? { + switch provider { + case .googleMaps: return googleState.cameraPosition + case .mapLibre: return mapLibreState.cameraPosition + case .mapKit: return mapKitState.cameraPosition + case .mapbox: return mapboxState.cameraPosition + case .arcGIS, .arcGIS2D: return arcGISState.cameraPosition + case .here: return hereState.cameraPosition + case .tomTom: return tomTomState.cameraPosition + case .mapTiler: return mapTilerState.cameraPosition + case .longdo: return longdoState.cameraPosition + } + } + + /// 選択中のプロバイダのカメラを動かす。制限が効いていれば、ここで外へ出そうとしても + /// ネイティブ API に拒否されるか、停止時にクランプで引き戻される。 + private func moveCamera(to position: GeoPoint, zoom: Double) { + let target = MapCameraPosition(position: position, zoom: zoom) + switch provider { + case .googleMaps: googleState.moveCameraTo(cameraPosition: target) + case .mapLibre: mapLibreState.moveCameraTo(cameraPosition: target) + case .mapKit: mapKitState.moveCameraTo(cameraPosition: target) + case .mapbox: mapboxState.moveCameraTo(cameraPosition: target) + case .arcGIS, .arcGIS2D: arcGISState.moveCameraTo(cameraPosition: target) + case .here: hereState.moveCameraTo(cameraPosition: target) + case .tomTom: tomTomState.moveCameraTo(cameraPosition: target) + case .mapTiler: mapTilerState.moveCameraTo(cameraPosition: target) + case .longdo: longdoState.moveCameraTo(cameraPosition: target) + } + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignMapComponent.swift index 57ef4e7..c1a33f2 100644 --- a/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignMapComponent.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI import UIKit @@ -13,6 +18,11 @@ struct MapDesignMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState var body: some View { SampleMapView( @@ -21,10 +31,11 @@ struct MapDesignMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState ) { MapViewContent() } diff --git a/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignMapPage.swift index ed7f0dc..a6c5afd 100644 --- a/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignMapPage.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI import UIKit @@ -17,6 +22,11 @@ struct MapDesignMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -36,6 +46,26 @@ struct MapDesignMapPage: View { _mapboxState = StateObject(wrappedValue: MapboxViewState( cameraPosition: vm.initCameraPosition )) + _arcGISState = StateObject(wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + )) + _hereState = StateObject(wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + )) + _tomTomState = StateObject(wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + )) + _mapTilerState = StateObject(wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + )) + _longdoState = StateObject(wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + )) } var body: some View { @@ -46,7 +76,12 @@ struct MapDesignMapPage: View { googleState: googleState, mapLibreState: mapLibreState, mapKitState: mapKitState, - mapboxState: mapboxState + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState ) // Message Card @@ -107,6 +142,26 @@ struct MapDesignMapPage: View { if let design = option.design as? MapboxMapDesign { mapboxState.mapDesignType = design } + case .arcGIS, .arcGIS2D: + if let design = option.design as? ArcGISDesign { + arcGISState.mapDesignType = design + } + case .here: + if let design = option.design as? HereMapDesign { + hereState.mapDesignType = design + } + case .tomTom: + if let design = option.design as? TomTomMapDesign { + tomTomState.mapDesignType = design + } + case .mapTiler: + if let design = option.design as? MapTilerDesign { + mapTilerState.mapDesignType = design + } + case .longdo: + if let design = option.design as? LongdoMapDesignType { + longdoState.mapDesignType = design + } } } } diff --git a/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignPageViewModel.swift index f6f341b..37d2c31 100644 --- a/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignPageViewModel.swift +++ b/samples/MapConductorSampleApp/Sources/pages/map/design/MapDesignPageViewModel.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo struct MapDesignOption { let label: String @@ -38,9 +43,54 @@ final class MapDesignPageViewModel: ObservableObject { mapDesignOptions = mapKitDesigns case .mapbox: mapDesignOptions = mapboxDesigns + case .arcGIS, .arcGIS2D: + mapDesignOptions = arcGISDesigns + case .here: + mapDesignOptions = hereDesign + case .tomTom: + mapDesignOptions = tomTomDesigns + case .mapTiler: + mapDesignOptions = mapTilerDesigns + case .longdo: + mapDesignOptions = longdoDesigns } } + private let longdoDesigns = [ + MapDesignOption(label: "Normal", design: LongdoDesign.Normal), + MapDesignOption(label: "Easy", design: LongdoDesign.Easy), + MapDesignOption(label: "Pastel", design: LongdoDesign.Pastel), + MapDesignOption(label: "PastelGray", design: LongdoDesign.PastelGray), + MapDesignOption(label: "Hard", design: LongdoDesign.Hard), + MapDesignOption(label: "Gray", design: LongdoDesign.Gray), + MapDesignOption(label: "Light", design: LongdoDesign.Light), + MapDesignOption(label: "Night", design: LongdoDesign.Night), + MapDesignOption(label: "Dark", design: LongdoDesign.Dark), + MapDesignOption(label: "Political", design: LongdoDesign.Political), + MapDesignOption(label: "Osm", design: LongdoDesign.Osm), + MapDesignOption(label: "Satellite", design: LongdoDesign.Satellite), + MapDesignOption(label: "Hybrid", design: LongdoDesign.Hybrid), + ] + + private let mapTilerDesigns = [ + MapDesignOption(label: "Streets", design: MapTilerDesign.Streets), + MapDesignOption(label: "StreetsLight", design: MapTilerDesign.StreetsLight), + MapDesignOption(label: "StreetsDark", design: MapTilerDesign.StreetsDark), + MapDesignOption(label: "Basic", design: MapTilerDesign.Basic), + MapDesignOption(label: "Bright", design: MapTilerDesign.Bright), + MapDesignOption(label: "Satellite", design: MapTilerDesign.Satellite), + MapDesignOption(label: "Outdoor", design: MapTilerDesign.Outdoor), + MapDesignOption(label: "Winter", design: MapTilerDesign.Winter), + MapDesignOption(label: "Topo", design: MapTilerDesign.Topo), + MapDesignOption(label: "Toner", design: MapTilerDesign.Toner), + MapDesignOption(label: "Dataviz", design: MapTilerDesign.Dataviz), + MapDesignOption(label: "Backdrop", design: MapTilerDesign.Backdrop), + MapDesignOption(label: "Ocean", design: MapTilerDesign.Ocean), + MapDesignOption(label: "Landscape", design: MapTilerDesign.Landscape), + MapDesignOption(label: "Aquarelle", design: MapTilerDesign.Aquarelle), + MapDesignOption(label: "OpenStreetMap", design: MapTilerDesign.OpenStreetMap), + ] + private let googleMapDesigns = [ MapDesignOption(label: "Normal", design: GoogleMapDesign.Normal), MapDesignOption(label: "Satellite", design: GoogleMapDesign.Satellite), @@ -82,4 +132,34 @@ final class MapDesignPageViewModel: ObservableObject { MapDesignOption(label: "Navigation Day", design: MapboxMapDesign.NavigationDay), MapDesignOption(label: "Navigation Night", design: MapboxMapDesign.NavigationNight), ] + + private let arcGISDesigns = [ + MapDesignOption(label: "Streets", design: ArcGISDesign.Streets), + MapDesignOption(label: "Imagery", design: ArcGISDesign.Imagery), + MapDesignOption(label: "Topographic", design: ArcGISDesign.Topographic), + MapDesignOption(label: "OpenStreetMap Standard", design: ArcGISDesign.OsmStandard), + ] + + private let tomTomDesigns = [ + MapDesignOption(label: "Standard", design: TomTomMapDesign.Standard), + MapDesignOption(label: "Driving", design: TomTomMapDesign.Driving), + MapDesignOption(label: "Satellite", design: TomTomMapDesign.Satellite), + ] + + private let hereDesign = [ + MapDesignOption(label: "NormalDay", design: HereMapDesign.NormalDay), + MapDesignOption(label: "NormalNight", design: HereMapDesign.NormalNight), + MapDesignOption(label: "Satellite", design: HereMapDesign.Satellite), + MapDesignOption(label: "HybridDay", design: HereMapDesign.HybridDay), + MapDesignOption(label: "HybridNight", design: HereMapDesign.HybridNight), + MapDesignOption(label: "LiteDay", design: HereMapDesign.LiteDay), + MapDesignOption(label: "LiteHybridDay", design: HereMapDesign.LiteHybridDay), + MapDesignOption(label: "LiteHybridNight", design: HereMapDesign.LiteHybridNight), + MapDesignOption(label: "LogisticsDay", design: HereMapDesign.LogisticsDay), + MapDesignOption(label: "LogisticsNight", design: HereMapDesign.LogisticsNight), + MapDesignOption(label: "LogisticsHybridDay", design: HereMapDesign.LogisticsHybridDay), + MapDesignOption(label: "LogisticsHybridNight", design: HereMapDesign.LogisticsHybridNight), + MapDesignOption(label: "RoadNetworkDay", design: HereMapDesign.RoadNetworkDay), + MapDesignOption(label: "RoadNetworkNight", design: HereMapDesign.RoadNetworkNight), + ] } diff --git a/samples/MapConductorSampleApp/Sources/pages/map/fitbounds/FitBoundsMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/map/fitbounds/FitBoundsMapComponent.swift new file mode 100644 index 0000000..8652b65 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/map/fitbounds/FitBoundsMapComponent.swift @@ -0,0 +1,52 @@ +import GoogleMaps +import MapConductorCore +import MapConductorForGoogleMaps +import MapConductorForMapLibre +import MapConductorForMapKit +import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo +import SwiftUI +import UIKit + +struct FitBoundsMapComponent: View { + @Binding var provider: MapProvider + @ObservedObject var googleState: GoogleMapViewState + @ObservedObject var mapLibreState: MapLibreViewState + @ObservedObject var mapKitState: MapKitViewState + @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState + + let marker: MarkerState + let boundsPolygon: PolygonState + var onMapLongClick: ((GeoPoint) -> Void)? = nil + + var body: some View { + SampleMapView( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + ) { + { () -> MapViewContent in + var content = MapViewContent() + content.markers = [Marker(state: marker)] + content.polygons = [Polygon(state: boundsPolygon)] + return content + }() + } + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/map/fitbounds/FitBoundsMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/map/fitbounds/FitBoundsMapPage.swift new file mode 100644 index 0000000..2d6c0a9 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/map/fitbounds/FitBoundsMapPage.swift @@ -0,0 +1,137 @@ +import MapConductorCore +import MapConductorForGoogleMaps +import MapConductorForMapLibre +import MapConductorForMapKit +import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo +import SwiftUI + +struct FitBoundsMapPage: View { + let onToggleSidebar: () -> Void + + @State private var provider: MapProvider + @StateObject private var viewModel: FitBoundsPageViewModel + + @StateObject private var googleState: GoogleMapViewState + @StateObject private var mapLibreState: MapLibreViewState + @StateObject private var mapKitState: MapKitViewState + @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState + + init(onToggleSidebar: @escaping () -> Void = {}) { + self.onToggleSidebar = onToggleSidebar + let vm = FitBoundsPageViewModel() + _viewModel = StateObject(wrappedValue: vm) + _provider = State(initialValue: MapProvider.initial()) + _googleState = StateObject( + wrappedValue: GoogleMapViewState(cameraPosition: vm.initCameraPosition) + ) + _mapLibreState = StateObject( + wrappedValue: MapLibreViewState( + mapDesignType: MapLibreDesign.DemoTiles, + cameraPosition: vm.initCameraPosition + ) + ) + _mapKitState = StateObject( + wrappedValue: MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapboxState = StateObject( + wrappedValue: MapboxViewState( + cameraPosition: vm.initCameraPosition + ) + ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) + } + + var body: some View { + DemoMapPageScaffold(provider: $provider, onToggleSidebar: onToggleSidebar) { + ZStack(alignment: .bottomLeading) { + FitBoundsMapComponent( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + marker: viewModel.marker, + boundsPolygon: viewModel.boundsPolygon, + ) + .onAppear { viewModel.setActiveState(activeState) } + .onChange(of: provider) { _ in viewModel.setActiveState(activeState) } + + VStack(alignment: .leading, spacing: 8) { + Text("Fit Bounds") + .font(.headline) + .foregroundColor(.primary) + Text("マーカーをドラッグして範囲を指定し、ドロップすると fitBounds で地図が移動します。") + .font(.subheadline) + .foregroundColor(.secondary) + } + .padding(16) + .background(Color(UIColor.systemBackground).opacity(0.95)) + .cornerRadius(12) + .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) + .padding(.leading, 16) + .padding(.bottom, 16) + } + } + } + + private var activeState: any MapViewStateProtocol { + switch provider { + case .googleMaps: return googleState + case .mapLibre: return mapLibreState + case .mapKit: return mapKitState + case .mapbox: return mapboxState + case .arcGIS, .arcGIS2D: return arcGISState + case .here: return hereState + case .tomTom: return tomTomState + case .mapTiler: return mapTilerState + case .longdo: return longdoState + } + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/map/fitbounds/FitBoundsPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/map/fitbounds/FitBoundsPageViewModel.swift new file mode 100644 index 0000000..ce12072 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/map/fitbounds/FitBoundsPageViewModel.swift @@ -0,0 +1,113 @@ +import Foundation +import MapConductorCore +import UIKit + +final class FitBoundsPageViewModel: ObservableObject { + let initCameraPosition: MapCameraPosition + let marker: MarkerState + let boundsPolygon: PolygonState + + private var dragStartPosition: GeoPoint? + private var clearTask: Task? + private var activeState: (any MapViewStateProtocol)? + + init() { + let initial = GeoPoint.fromLatLong(latitude: 35.68, longitude: 139.76) + self.initCameraPosition = MapCameraPosition( + position: initial, + zoom: 10.0, + bearing: 0.0, + tilt: 0.0, + paddings: nil + ) + self.boundsPolygon = PolygonState( + points: Self.hiddenPolygonPoints(around: initial), + id: "fitbounds_polygon", + strokeColor: .clear, + strokeWidth: 0.0, + fillColor: .clear + ) + self.marker = MarkerState( + position: initial, + id: "fitbounds_marker", + draggable: true + ) + marker.onDragStart = { [weak self] m in self?.onDragStart(m) } + marker.onDrag = { [weak self] m in self?.onDrag(m) } + marker.onDragEnd = { [weak self] m in self?.onDragEnd(m) } + } + + func setActiveState(_ state: any MapViewStateProtocol) { + activeState = state + } + + private func onDragStart(_ marker: MarkerState) { + clearTask?.cancel() + clearTask = nil + dragStartPosition = GeoPoint.from(position: marker.position) + } + + private func onDrag(_ marker: MarkerState) { + guard let start = dragStartPosition else { return } + let current = GeoPoint.from(position: marker.position) + showBoundsPolygon(points: Self.polygonPoints(from: start, to: current)) + } + + private func onDragEnd(_ marker: MarkerState) { + guard let start = dragStartPosition else { return } + let current = GeoPoint.from(position: marker.position) + let bounds = GeoRectBounds( + southWest: GeoPoint( + latitude: min(start.latitude, current.latitude), + longitude: min(start.longitude, current.longitude) + ), + northEast: GeoPoint( + latitude: max(start.latitude, current.latitude), + longitude: max(start.longitude, current.longitude) + ) + ) + activeState?.fitBounds(bounds: bounds, padding: 0) + dragStartPosition = nil + clearTask?.cancel() + clearTask = Task { @MainActor [weak self] in + try? await Task.sleep(nanoseconds: 1_500_000_000) + guard let self else { return } + self.hideBoundsPolygon(around: GeoPoint.from(position: marker.position)) + } + } + + private func showBoundsPolygon(points: [GeoPointProtocol]) { + boundsPolygon.points = points + boundsPolygon.strokeColor = .red + boundsPolygon.strokeWidth = 2.0 + boundsPolygon.fillColor = UIColor.red.withAlphaComponent(0.3) + } + + private func hideBoundsPolygon(around point: GeoPoint) { + boundsPolygon.points = Self.hiddenPolygonPoints(around: point) + boundsPolygon.strokeColor = .clear + boundsPolygon.strokeWidth = 0.0 + boundsPolygon.fillColor = .clear + } + + private static func hiddenPolygonPoints(around point: GeoPoint) -> [GeoPointProtocol] { + let epsilon = 0.000001 + return polygonPoints( + from: point, + to: GeoPoint(latitude: point.latitude + epsilon, longitude: point.longitude + epsilon) + ) + } + + private static func polygonPoints(from a: GeoPoint, to b: GeoPoint) -> [GeoPointProtocol] { + let minLat = min(a.latitude, b.latitude) + let maxLat = max(a.latitude, b.latitude) + let minLon = min(a.longitude, b.longitude) + let maxLon = max(a.longitude, b.longitude) + return [ + GeoPoint(latitude: minLat, longitude: minLon), + GeoPoint(latitude: minLat, longitude: maxLon), + GeoPoint(latitude: maxLat, longitude: maxLon), + GeoPoint(latitude: maxLat, longitude: minLon), + ] + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/map/flyto/FlyToMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/map/flyto/FlyToMapComponent.swift index 0878821..b8fc761 100644 --- a/samples/MapConductorSampleApp/Sources/pages/map/flyto/FlyToMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/map/flyto/FlyToMapComponent.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI import UIKit @@ -13,6 +18,11 @@ struct FlyToMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let polylines: [PolylineState] let markers: [MarkerState] @@ -24,10 +34,11 @@ struct FlyToMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/map/flyto/FlyToMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/map/flyto/FlyToMapPage.swift index f7f8b3d..1973153 100644 --- a/samples/MapConductorSampleApp/Sources/pages/map/flyto/FlyToMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/map/flyto/FlyToMapPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct FlyToMapPage: View { @@ -15,6 +20,11 @@ struct FlyToMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -39,6 +49,36 @@ struct FlyToMapPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -50,6 +90,11 @@ struct FlyToMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, polylines: viewModel.polylines, markers: viewModel.markers ) @@ -105,7 +150,7 @@ struct FlyToMapPage: View { } } - private var activeState: MapViewStateProtocol { + private var activeState: any MapViewStateProtocol { switch provider { case .googleMaps: return googleState @@ -115,6 +160,16 @@ struct FlyToMapPage: View { return mapKitState case .mapbox: return mapboxState + case .arcGIS, .arcGIS2D: + return arcGISState + case .here: + return hereState + case .tomTom: + return tomTomState + case .mapTiler: + return mapTilerState + case .longdo: + return longdoState } } } diff --git a/samples/MapConductorSampleApp/Sources/pages/map/stylereload/StyleReloadPage.swift b/samples/MapConductorSampleApp/Sources/pages/map/stylereload/StyleReloadPage.swift new file mode 100644 index 0000000..5c35db7 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/map/stylereload/StyleReloadPage.swift @@ -0,0 +1,221 @@ +import MapConductorCore +import MapConductorForMapLibre +import MapConductorForMapTiler +import MapConductorForMapbox +import SwiftUI + +/// `StyleReloadUITests` が実機で使う計測ページ。 +/// +/// Mapbox / MapLibre / MapTiler は地図デザインを変えると新しいスタイルを読み込み、 +/// ランタイムに足したソース・レイヤ・画像がすべて捨てられる。オーバーレイがその後 +/// ちゃんと作り直されるかを確かめる。プロバイダは +/// `MAPCONDUCTOR_SAMPLE_PROVIDER`(mapbox / maplibre / maptiler)で選ぶ。 +/// +/// アクセシビリティ経由で次を公開する: +/// +/// - `styleReloadAnchor` マーカーのアンカー(ウィンドウ座標・pt) +/// - `styleReloadResult` `":HIT"` / `":MISS"`。タップごとに seq が増える +/// - `styleReloadDesign` 現在の地図デザイン id +/// - `styleReloadToggle` デザインを切り替えるボタン +/// +/// 判定にタップを使うのは、当たり判定が**実際に描かれているもの**を見るため。 +/// マネージャが状態を持っているだけでは HIT にならない。 +/// `HereHitTestPage` と同じ計測ページの作りに合わせてある。 +struct StyleReloadPage: View { + static let center = GeoPoint(latitude: 35.681236, longitude: 139.767125) + + /// 中央のマーカーから離した位置に他のオーバーレイを置く(当たり判定が被らないように)。 + private static let ring: [GeoPoint] = [ + GeoPoint(latitude: 35.6870, longitude: 139.7620), + GeoPoint(latitude: 35.6870, longitude: 139.7720), + GeoPoint(latitude: 35.6790, longitude: 139.7720), + GeoPoint(latitude: 35.6790, longitude: 139.7620), + ] + + private static var selectedProvider: String { + ProcessInfo.processInfo.environment["MAPCONDUCTOR_SAMPLE_PROVIDER"]?.lowercased() ?? "mapbox" + } + + let onToggleSidebar: () -> Void + + @StateObject private var mapboxState = MapboxViewState( + mapDesignType: MapboxMapDesign.Streets, + cameraPosition: MapCameraPosition(position: StyleReloadPage.center, zoom: 14.0) + ) + @StateObject private var mapLibreState = MapLibreViewState( + mapDesignType: MapLibreDesign.OsmBright, + cameraPosition: MapCameraPosition(position: StyleReloadPage.center, zoom: 14.0) + ) + @StateObject private var mapTilerState = MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: MapCameraPosition(position: StyleReloadPage.center, zoom: 14.0) + ) + + /// ラスタレイヤの配信元。判定は画面の色で行うので、ここは**タイルを配るだけ**。 + /// + /// 受け取った数を画面に出してはいけない。数は増え続けるので表示が止まらず、 + /// UI が静止しないまま `XCUIElement.tap()` が撃たれてボタンを外す + /// (実機で「トグルを押したのに地図がクリックされた」形で踏んだ)。 + @StateObject private var rasterProbe = StyleReloadRasterProbe() + + @State private var seq: Int = 0 + @State private var result: String = "NONE" + @State private var anchor: String = "?" + @State private var mapFrame: CGRect = .zero + + private let refresh = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect() + + var body: some View { + ZStack(alignment: .top) { + GeometryReader { geo in + mapView + .onAppear { mapFrame = geo.frame(in: .global) } + .onChange(of: geo.frame(in: .global)) { _, newFrame in mapFrame = newFrame } + } + + // 読み出し用。テストは中央付近しかタップしないので上端に寄せておく。 + VStack(spacing: 0) { + Text(anchor).accessibilityIdentifier("styleReloadAnchor") + Text(result).accessibilityIdentifier("styleReloadResult") + Text(currentDesignId).accessibilityIdentifier("styleReloadDesign") + Button("Toggle design") { toggleDesign() } + .accessibilityIdentifier("styleReloadToggle") + } + .font(.caption2) + .opacity(0.85) + } + .onReceive(refresh) { _ in updateAnchor() } + } + + @ViewBuilder + private var mapView: some View { + switch Self.selectedProvider { + case "maplibre": + MapLibreMapView( + state: mapLibreState, + onMapClick: { _ in record("MISS") }, + sdkInitialize: SampleMapView.initializeAllSDKs + ) { overlays } + case "maptiler": + MapTilerMapView( + state: mapTilerState, + onMapClick: { _ in record("MISS") }, + sdkInitialize: SampleMapView.initializeAllSDKs + ) { overlays } + default: + MapboxMapView( + state: mapboxState, + onMapClick: { _ in record("MISS") }, + sdkInitialize: SampleMapView.initializeAllSDKs + ) { overlays } + } + } + + @MapViewContentBuilder + private var overlays: MapViewContent { + Marker(state: MarkerState( + position: StyleReloadPage.center, + id: "stylereload_target", + icon: DefaultMarkerIcon(), + onClick: { _ in record("HIT") } + )) + Circle( + center: GeoPoint(latitude: 35.6840, longitude: 139.7600), + radiusMeters: 200, + strokeColor: .blue, + fillColor: UIColor.blue.withAlphaComponent(0.3) + ) + Polyline( + points: StyleReloadPage.ring, + strokeColor: .red, + strokeWidth: 4 + ) + Polygon( + points: [ + GeoPoint(latitude: 35.6760, longitude: 139.7640), + GeoPoint(latitude: 35.6760, longitude: 139.7700), + GeoPoint(latitude: 35.6730, longitude: 139.7670), + ], + strokeColor: .green, + fillColor: UIColor.green.withAlphaComponent(0.4) + ) + RasterLayer(state: rasterProbe.rasterLayerState) + } + + private var currentDesignId: String { + switch Self.selectedProvider { + case "maplibre": return mapLibreState.mapDesignType.id + case "maptiler": return mapTilerState.mapDesignType.id + default: return mapboxState.mapDesignType.id + } + } + + /// スタイル再読込を起こす。切り替え先はベクタタイルの別スタイルにしておく + /// (衛星画像だとマーカーが見えていても目視確認しづらいため)。 + private func toggleDesign() { + switch Self.selectedProvider { + case "maplibre": + mapLibreState.mapDesignType = + mapLibreState.mapDesignType.id == MapLibreDesign.OsmBright.id + ? MapLibreDesign.DemoTiles + : MapLibreDesign.OsmBright + case "maptiler": + mapTilerState.mapDesignType = + mapTilerState.mapDesignType.id == MapTilerDesign.Streets.id + ? MapTilerDesign.Bright + : MapTilerDesign.Streets + default: + mapboxState.mapDesignType = + mapboxState.mapDesignType.id == MapboxMapDesign.Streets.id + ? MapboxMapDesign.Outdoors + : MapboxMapDesign.Streets + } + } + + private func updateAnchor() { + let holder: AnyMapViewHolder? + switch Self.selectedProvider { + case "maplibre": holder = mapLibreState.getMapViewHolder() + case "maptiler": holder = mapTilerState.getMapViewHolder() + default: holder = mapboxState.getMapViewHolder() + } + guard let holder, let local = holder.toScreenOffset(position: StyleReloadPage.center) else { + anchor = "?" + return + } + anchor = String( + format: "%.2f,%.2f", + mapFrame.origin.x + local.x, + mapFrame.origin.y + local.y + ) + } + + private func record(_ outcome: String) { + seq += 1 + result = "\(seq):\(outcome)" + } +} + +/// ラスタレイヤ用の計測サーバとレイヤの寿命を握る。 +/// +/// `View` の struct に持たせると再構築のたびにサーバが立ち上がるので、 +/// `@StateObject` にして 1 回だけ作る。 +@MainActor +final class StyleReloadRasterProbe: ObservableObject { + private let server = HeaderRecordingTileServer.start() + let rasterLayerState: RasterLayerState + + init() { + rasterLayerState = RasterLayerState( + source: .urlTemplate(template: server.urlTemplate(), tileSize: 256), + // タイル画像自体が α 0.35 のマゼンタなので、レイヤ側は下げない。 + // 薄くすると画面平均の色の偏りが小さくなり、判定の余裕が無くなる。 + opacity: 1.0, + id: "stylereload_raster" + ) + } + + deinit { + server.stop() + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/map/tilt/TiltMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/map/tilt/TiltMapPage.swift new file mode 100644 index 0000000..3d8a2b4 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/map/tilt/TiltMapPage.swift @@ -0,0 +1,261 @@ +import MapConductorCore +import MapConductorForArcGIS +import MapConductorForGoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo +import MapConductorForMapKit +import MapConductorForMapLibre +import MapConductorForMapbox +import SwiftUI +import UIKit + +struct TiltMapPage: View { + let onToggleSidebar: () -> Void + + @State private var provider: MapProvider + @StateObject private var viewModel: TiltMapPageViewModel + @StateObject private var googleState: GoogleMapViewState + @StateObject private var mapLibreState: MapLibreViewState + @StateObject private var mapKitState: MapKitViewState + @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState + + init(onToggleSidebar: @escaping () -> Void = {}) { + self.onToggleSidebar = onToggleSidebar + let viewModel = TiltMapPageViewModel() + _viewModel = StateObject(wrappedValue: viewModel) + _provider = State(initialValue: MapProvider.initial()) + _googleState = StateObject( + wrappedValue: GoogleMapViewState(cameraPosition: viewModel.initCameraPosition) + ) + _mapLibreState = StateObject( + wrappedValue: MapLibreViewState( + mapDesignType: MapLibreDesign.OsmBright, + cameraPosition: viewModel.initCameraPosition + ) + ) + _mapKitState = StateObject( + wrappedValue: MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, + cameraPosition: viewModel.initCameraPosition + ) + ) + _mapboxState = StateObject( + wrappedValue: MapboxViewState( + cameraPosition: viewModel.initCameraPosition, + ) + ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: viewModel.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: viewModel.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: viewModel.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: viewModel.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: viewModel.initCameraPosition + ) + ) + } + + var body: some View { + DemoMapPageScaffold(provider: $provider, onToggleSidebar: onToggleSidebar) { + ZStack(alignment: .bottomLeading) { + SampleMapView( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onCameraMoveStart: viewModel.onMapCameraMoveStart, + onCameraMoveEnd: viewModel.onMapCameraMoveEnd + ) { + MapViewContent() + } + .onAppear { viewModel.onMapViewChanged(activeState) } + .onChange(of: provider) { _ in viewModel.onMapViewChanged(activeState) } + + VStack(spacing: 6) { + TiltCameraDiagram(tilt: viewModel.tilt) + .frame(height: 120) + + HStack { + Text(String(format: "tilt: %.2f", viewModel.tilt)) + .fixedSize() + Slider( + value: Binding( + get: { viewModel.tilt }, + set: { viewModel.setTilt($0, state: activeState) } + ), + in: -60.0...60.0, + onEditingChanged: viewModel.setTiltEditing + ) + .disabled(viewModel.disableSlider) + } + } + .padding(6) + .frame(maxWidth: 600) + .background(Color(UIColor.systemBackground).opacity(0.95)) + .cornerRadius(12) + .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) + .padding(.horizontal, 16) + .padding(.bottom, 16) + } + } + } + + private var activeState: any MapViewStateProtocol { + switch provider { + case .googleMaps: return googleState + case .mapLibre: return mapLibreState + case .mapKit: return mapKitState + case .mapbox: return mapboxState + case .arcGIS, .arcGIS2D: return arcGISState + case .here: return hereState + case .tomTom: return tomTomState + case .mapTiler: return mapTilerState + case .longdo: return longdoState + } + } +} + +private struct TiltCameraDiagram: View { + let tilt: Double + + var body: some View { + Canvas { context, size in + let width = size.width + let height = size.height + let groundY = height * 0.78 + let originX = width * 0.5 + let cameraY = height * 0.22 + let tiltRadians = min(abs(tilt), 90.0) * .pi / 180.0 + let targetDistance = min((groundY - cameraY) * tan(tiltRadians), width * 0.44) + let targetX = tilt < 0.0 ? originX - targetDistance : originX + let cameraX = tilt > 0.0 ? originX + targetDistance : originX + + strokeLine( + in: &context, + from: CGPoint(x: width * 0.08, y: groundY), + to: CGPoint(x: width * 0.94, y: groundY), + color: Color(red: 0.89, green: 0.88, blue: 0.93), + width: 3, + lineCap: .round + ) + strokeLine( + in: &context, + from: CGPoint(x: cameraX, y: cameraY), + to: CGPoint(x: cameraX, y: groundY), + color: Color(red: 0.56, green: 0.53, blue: 0.60), + width: 2 + ) + fillCircle( + in: &context, + center: CGPoint(x: cameraX, y: cameraY), + radius: 8, + color: Color(red: 0.36, green: 0.65, blue: 1.0) + ) + fillCircle( + in: &context, + center: CGPoint(x: targetX, y: groundY), + radius: 7, + color: Color(red: 1.0, green: 0.38, blue: 0.35) + ) + strokeLine( + in: &context, + from: CGPoint(x: cameraX, y: cameraY), + to: CGPoint(x: tilt == 0.0 ? cameraX : targetX, y: groundY), + color: Color(red: 1.0, green: 0.78, blue: 0.34), + width: 4, + lineCap: .round + ) + + var cameraBody = Path() + cameraBody.move(to: CGPoint(x: cameraX - 12, y: cameraY - 8)) + cameraBody.addLine(to: CGPoint(x: cameraX + 14, y: cameraY - 4)) + cameraBody.addLine(to: CGPoint(x: cameraX + 10, y: cameraY + 10)) + cameraBody.addLine(to: CGPoint(x: cameraX - 12, y: cameraY + 8)) + cameraBody.closeSubpath() + context.fill(cameraBody, with: .color(Color(red: 0.18, green: 0.16, blue: 0.22))) + context.stroke(cameraBody, with: .color(.white.opacity(0.7)), lineWidth: 1.5) + + fillCircle( + in: &context, + center: CGPoint(x: originX, y: groundY), + radius: 3.5, + color: Color(red: 0.56, green: 0.53, blue: 0.60) + ) + strokeLine( + in: &context, + from: CGPoint(x: min(cameraX, targetX), y: groundY + 12), + to: CGPoint(x: max(cameraX, targetX), y: groundY + 12), + color: Color(red: 0.72, green: 0.69, blue: 0.79), + width: 2, + lineCap: .round + ) + } + } + + private func strokeLine( + in context: inout GraphicsContext, + from start: CGPoint, + to end: CGPoint, + color: Color, + width: CGFloat, + lineCap: CGLineCap = .butt + ) { + var path = Path() + path.move(to: start) + path.addLine(to: end) + context.stroke( + path, + with: .color(color), + style: StrokeStyle(lineWidth: width, lineCap: lineCap) + ) + } + + private func fillCircle( + in context: inout GraphicsContext, + center: CGPoint, + radius: CGFloat, + color: Color + ) { + let rect = CGRect( + x: center.x - radius, + y: center.y - radius, + width: radius * 2, + height: radius * 2 + ) + context.fill(Path(ellipseIn: rect), with: .color(color)) + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/map/tilt/TiltMapPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/map/tilt/TiltMapPageViewModel.swift new file mode 100644 index 0000000..2c297fa --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/map/tilt/TiltMapPageViewModel.swift @@ -0,0 +1,54 @@ +import Foundation +import MapConductorCore + +@MainActor +final class TiltMapPageViewModel: ObservableObject { + let initCameraPosition = MapCameraPosition( + position: GeoPoint( + latitude: 48.858140690309604, + longitude: 2.2945027576710344 + ), + zoom: 17.0, + bearing: 270.0 + ) + + @Published private(set) var tilt: Double = 0.0 + @Published private(set) var disableSlider = false + + private var currentPosition: MapCameraPosition + private var isEditingTilt = false + + init() { + currentPosition = initCameraPosition + tilt = initCameraPosition.tilt + } + + func onMapViewChanged(_ state: any MapViewStateProtocol) { + state.moveCameraTo(cameraPosition: currentPosition) + } + + func setTilt(_ angle: Double, state: any MapViewStateProtocol) { + guard !disableSlider else { return } + + tilt = angle + currentPosition = currentPosition.copy(tilt: angle) + state.moveCameraTo(cameraPosition: currentPosition) + } + + func setTiltEditing(_ isEditing: Bool) { + isEditingTilt = isEditing + } + + func onMapCameraMoveStart(_ position: MapCameraPosition) { + guard !isEditingTilt else { return } + disableSlider = true + } + + func onMapCameraMoveEnd(_ position: MapCameraPosition) { + currentPosition = position + if !isEditingTilt { + tilt = position.tilt + } + disableSlider = false + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/map/uisettings/UISettingsPage.swift b/samples/MapConductorSampleApp/Sources/pages/map/uisettings/UISettingsPage.swift new file mode 100644 index 0000000..b21c23a --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/map/uisettings/UISettingsPage.swift @@ -0,0 +1,132 @@ +import GoogleMaps +import MapConductorCore +import MapConductorForArcGIS +import MapConductorForGoogleMaps +import MapConductorForHERE +import MapConductorForLongdo +import MapConductorForMapKit +import MapConductorForMapLibre +import MapConductorForMapTiler +import MapConductorForMapbox +import MapConductorForTomTom +import SwiftUI + +/// Exercises `MapUISettings` against a live map — the iOS counterpart of the +/// example-app's "UI Settings" page. +/// +/// Four toggles flip the gesture flags. The camera is published as an +/// accessibility label so a device run can tell whether a swipe actually moved the +/// map, which is the only way to check providers whose gesture state cannot be +/// read back (Longdo, MapTiler). +struct UISettingsPage: View { + let onToggleSidebar: () -> Void + + init(onToggleSidebar: @escaping () -> Void = {}) { + self.onToggleSidebar = onToggleSidebar + } + + @State private var provider: MapProvider = MapProvider.initial() + @State private var settings = UISettingsPage.initialSettings() + + /// Lets a UI test preset the flags instead of driving the toggles, which is + /// far more reliable than tapping a SwiftUI Toggle by coordinate. + private static func initialSettings() -> MapUISettings { + ProcessInfo.processInfo.environment["MAPCONDUCTOR_SAMPLE_UI_GESTURES"] == "none" + ? .None + : .Default + } + @State private var cameraText = "?" + + private static let start = MapCameraPosition( + position: GeoPoint(latitude: 35.681236, longitude: 139.767125), + zoom: 14, + bearing: 20, + tilt: 30 + ) + + @StateObject private var googleState = GoogleMapViewState(cameraPosition: start) + @StateObject private var mapLibreState = MapLibreViewState( + mapDesignType: MapLibreDesign.DemoTiles, cameraPosition: start) + @StateObject private var mapKitState = MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, cameraPosition: start) + @StateObject private var mapboxState = MapboxViewState(cameraPosition: start) + @StateObject private var arcGISState = ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, cameraPosition: start) + @StateObject private var hereState = HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, cameraPosition: start) + @StateObject private var tomTomState = TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, cameraPosition: start) + @StateObject private var mapTilerState = MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, cameraPosition: start) + @StateObject private var longdoState = LongdoViewState( + mapDesignType: LongdoDesign.Normal, cameraPosition: start) + + var body: some View { + DemoMapPageScaffold(provider: $provider, onToggleSidebar: onToggleSidebar) { + SampleMapView( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onCameraMove: { camera in + cameraText = String( + format: "%.5f,%.5f,%.2f,%.1f,%.1f", + camera.position.latitude, + camera.position.longitude, + camera.zoom, + camera.bearing, + camera.tilt + ) + } + ) {} + + VStack(alignment: .leading, spacing: 2) { + toggle("scrollGesture", \.scrollGesture) + toggle("zoomGesture", \.zoomGesture) + toggle("rotateGesture", \.rotateGesture) + toggle("tiltGesture", \.tiltGesture) + Text(cameraText) + .font(.caption2) + .accessibilityIdentifier("cameraReadout") + } + .padding(10) + .background(.thinMaterial) + .padding(10) + } + .onChange(of: settings) { _, new in apply(new) } + .onAppear { apply(settings) } + } + + private func toggle( + _ label: String, + _ key: WritableKeyPath + ) -> some View { + Toggle(label, isOn: Binding( + get: { settings[keyPath: key] }, + set: { settings[keyPath: key] = $0 } + )) + .accessibilityIdentifier(label) + .font(.caption) + .frame(maxWidth: 260) + } + + /// Every provider state carries its own `uiSettings`, so push to all of them — + /// switching provider then keeps the current flags. + private func apply(_ new: MapUISettings) { + googleState.uiSettings = new + mapLibreState.uiSettings = new + mapKitState.uiSettings = new + mapboxState.uiSettings = new + arcGISState.uiSettings = new + hereState.uiSettings = new + tomTomState.uiSettings = new + mapTilerState.uiSettings = new + longdoState.uiSettings = new + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/map/visibleregion/VisibleRegionMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/map/visibleregion/VisibleRegionMapComponent.swift index 2e0b7b7..d47a4d8 100644 --- a/samples/MapConductorSampleApp/Sources/pages/map/visibleregion/VisibleRegionMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/map/visibleregion/VisibleRegionMapComponent.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI import UIKit @@ -13,6 +18,11 @@ struct VisibleRegionMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let onCameraChanged: ((MapCameraPosition) -> Void)? @@ -20,6 +30,14 @@ struct VisibleRegionMapComponent: View { @State private var visibleRegionInfo: VisibleRegionInfo? @State private var isExpanded = false + @State private var centerMarkerState = MarkerState(position: GeoPoint(latitude: 0, longitude: 0), id: "vr_center", icon: DefaultMarkerIcon(fillColor: .red, label: "C")) + @State private var swMarkerState = MarkerState(position: GeoPoint(latitude: 0, longitude: 0), id: "vr_sw", icon: DefaultMarkerIcon(fillColor: .black, label: "SW")) + @State private var neMarkerState = MarkerState(position: GeoPoint(latitude: 0, longitude: 0), id: "vr_ne", icon: DefaultMarkerIcon(fillColor: .black, label: "NE")) + @State private var nlMarkerState = MarkerState(position: GeoPoint(latitude: 0, longitude: 0), id: "vr_nl", icon: DefaultMarkerIcon(fillColor: .blue, label: "NL")) + @State private var nrMarkerState = MarkerState(position: GeoPoint(latitude: 0, longitude: 0), id: "vr_nr", icon: DefaultMarkerIcon(fillColor: .green, label: "NR")) + @State private var flMarkerState = MarkerState(position: GeoPoint(latitude: 0, longitude: 0), id: "vr_fl", icon: DefaultMarkerIcon(fillColor: .systemYellow, label: "FL")) + @State private var frMarkerState = MarkerState(position: GeoPoint(latitude: 0, longitude: 0), id: "vr_fr", icon: DefaultMarkerIcon(fillColor: .magenta, label: "FR")) + var body: some View { ZStack(alignment: .bottomLeading) { SampleMapView( @@ -28,16 +46,17 @@ struct VisibleRegionMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, onCameraMoveEnd: { pos in cameraPosition = pos onCameraChanged?(pos) if let vr = pos.visibleRegion { visibleRegionInfo = makeInfo(vr) } - }, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) } ) { { () -> MapViewContent in @@ -47,46 +66,29 @@ struct VisibleRegionMapComponent: View { if !bounds.isEmpty, let sw = bounds.southWest, let ne = bounds.northEast { let centerLat = (sw.latitude + ne.latitude) / 2 let centerLng = (sw.longitude + ne.longitude) / 2 + centerMarkerState.position = GeoPoint(latitude: centerLat, longitude: centerLng) + swMarkerState.position = sw + neMarkerState.position = ne content.markers = [ - Marker(state: MarkerState( - position: GeoPoint(latitude: centerLat, longitude: centerLng), - id: "vr_center", - icon: DefaultMarkerIcon(fillColor: .red, label: "C") - )), - Marker(state: MarkerState( - position: sw, - id: "vr_sw", - icon: DefaultMarkerIcon(fillColor: .black, label: "SW") - )), - Marker(state: MarkerState( - position: ne, - id: "vr_ne", - icon: DefaultMarkerIcon(fillColor: .black, label: "NE") - )) + Marker(state: centerMarkerState), + Marker(state: swMarkerState), + Marker(state: neMarkerState) ] if let p = vr.nearLeft { - content.markers.append(Marker(state: MarkerState( - position: p, id: "vr_nl", - icon: DefaultMarkerIcon(fillColor: .blue, label: "NL") - ))) + nlMarkerState.position = p + content.markers.append(Marker(state: nlMarkerState)) } if let p = vr.nearRight { - content.markers.append(Marker(state: MarkerState( - position: p, id: "vr_nr", - icon: DefaultMarkerIcon(fillColor: .green, label: "NR") - ))) + nrMarkerState.position = p + content.markers.append(Marker(state: nrMarkerState)) } if let p = vr.farLeft { - content.markers.append(Marker(state: MarkerState( - position: p, id: "vr_fl", - icon: DefaultMarkerIcon(fillColor: .systemYellow, label: "FL") - ))) + flMarkerState.position = p + content.markers.append(Marker(state: flMarkerState)) } if let p = vr.farRight { - content.markers.append(Marker(state: MarkerState( - position: p, id: "vr_fr", - icon: DefaultMarkerIcon(fillColor: .magenta, label: "FR") - ))) + frMarkerState.position = p + content.markers.append(Marker(state: frMarkerState)) } } } diff --git a/samples/MapConductorSampleApp/Sources/pages/map/visibleregion/VisibleRegionPage.swift b/samples/MapConductorSampleApp/Sources/pages/map/visibleregion/VisibleRegionPage.swift index b6db2b1..11f543d 100644 --- a/samples/MapConductorSampleApp/Sources/pages/map/visibleregion/VisibleRegionPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/map/visibleregion/VisibleRegionPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct VisibleRegionPage: View { @@ -15,6 +20,11 @@ struct VisibleRegionPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -35,6 +45,26 @@ struct VisibleRegionPage: View { _mapboxState = StateObject(wrappedValue: MapboxViewState( cameraPosition: initCamera )) + _arcGISState = StateObject(wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: initCamera + )) + _hereState = StateObject(wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: initCamera + )) + _tomTomState = StateObject(wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: initCamera + )) + _mapTilerState = StateObject(wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: initCamera + )) + _longdoState = StateObject(wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: initCamera + )) } var body: some View { @@ -45,6 +75,11 @@ struct VisibleRegionPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, onCameraChanged: { camera in viewModel.onCameraChanged(camera) } diff --git a/samples/MapConductorSampleApp/Sources/pages/marker/animation/AnimationMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/marker/animation/AnimationMapComponent.swift index 0261149..383bfa5 100644 --- a/samples/MapConductorSampleApp/Sources/pages/marker/animation/AnimationMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/marker/animation/AnimationMapComponent.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForLongdo import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import SwiftUI import UIKit @@ -13,6 +18,11 @@ struct AnimationMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let allMarkers: [MarkerState] let onMapClick: (GeoPoint) -> Void @@ -24,11 +34,12 @@ struct AnimationMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - onMapClick: onMapClick, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onMapClick: onMapClick ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/marker/animation/AnimationMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/marker/animation/AnimationMapPage.swift index 53b737f..9a87f36 100644 --- a/samples/MapConductorSampleApp/Sources/pages/marker/animation/AnimationMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/marker/animation/AnimationMapPage.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForLongdo import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import SwiftUI import UIKit @@ -17,6 +22,11 @@ struct AnimationMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -40,6 +50,36 @@ struct AnimationMapPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -51,6 +91,11 @@ struct AnimationMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, allMarkers: viewModel.allMarkers, onMapClick: { _ in } ) diff --git a/samples/MapConductorSampleApp/Sources/pages/marker/icons/MarkerBasicMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/marker/icons/MarkerBasicMapComponent.swift index c84ed86..0df4042 100644 --- a/samples/MapConductorSampleApp/Sources/pages/marker/icons/MarkerBasicMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/marker/icons/MarkerBasicMapComponent.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI import UIKit @@ -13,6 +18,11 @@ struct MarkerBasicMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState @State private var selectedSnippet: String? @@ -24,11 +34,12 @@ struct MarkerBasicMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - onMapClick: { _ in selectedSnippet = nil }, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onMapClick: { _ in selectedSnippet = nil } ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/marker/icons/MarkerBasicPage.swift b/samples/MapConductorSampleApp/Sources/pages/marker/icons/MarkerBasicPage.swift index fe46a4b..57665e5 100644 --- a/samples/MapConductorSampleApp/Sources/pages/marker/icons/MarkerBasicPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/marker/icons/MarkerBasicPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct MarkerBasicPage: View { @@ -14,6 +19,11 @@ struct MarkerBasicPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -34,6 +44,26 @@ struct MarkerBasicPage: View { _mapboxState = StateObject(wrappedValue: MapboxViewState( cameraPosition: initCamera )) + _arcGISState = StateObject(wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: initCamera + )) + _hereState = StateObject(wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: initCamera + )) + _tomTomState = StateObject(wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: initCamera + )) + _mapTilerState = StateObject(wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: initCamera + )) + _longdoState = StateObject(wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: initCamera + )) } var body: some View { @@ -43,7 +73,12 @@ struct MarkerBasicPage: View { googleState: googleState, mapLibreState: mapLibreState, mapKitState: mapKitState, - mapboxState: mapboxState + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState ) } } diff --git a/samples/MapConductorSampleApp/Sources/pages/marker/postoffice/PostOfficeMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/marker/postoffice/PostOfficeMapComponent.swift index 05d1ebd..656e4db 100644 --- a/samples/MapConductorSampleApp/Sources/pages/marker/postoffice/PostOfficeMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/marker/postoffice/PostOfficeMapComponent.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct PostOfficeMapComponent: View { @@ -12,6 +17,11 @@ struct PostOfficeMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let markers: [MarkerState] let selectedMarker: MarkerState? @@ -25,11 +35,12 @@ struct PostOfficeMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - onMapClick: onMapClick, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onMapClick: onMapClick ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/marker/postoffice/PostOfficePage.swift b/samples/MapConductorSampleApp/Sources/pages/marker/postoffice/PostOfficePage.swift index 56bb8b9..a012eb0 100644 --- a/samples/MapConductorSampleApp/Sources/pages/marker/postoffice/PostOfficePage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/marker/postoffice/PostOfficePage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct PostOfficePage: View { @@ -15,6 +20,11 @@ struct PostOfficePage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -35,6 +45,26 @@ struct PostOfficePage: View { _mapboxState = StateObject(wrappedValue: MapboxViewState( cameraPosition: vm.initCameraPosition )) + _arcGISState = StateObject(wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + )) + _hereState = StateObject(wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + )) + _tomTomState = StateObject(wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + )) + _mapTilerState = StateObject(wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + )) + _longdoState = StateObject(wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + )) } var body: some View { @@ -46,6 +76,11 @@ struct PostOfficePage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, markers: viewModel.markers, selectedMarker: viewModel.selectedMarker, onMapClick: { _ in viewModel.clearSelection() }, @@ -81,6 +116,16 @@ struct PostOfficePage: View { mapKitState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) case .mapbox: mapboxState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) + case .arcGIS, .arcGIS2D: + arcGISState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) + case .here: + hereState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) + case .tomTom: + tomTomState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) + case .mapTiler: + mapTilerState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) + case .longdo: + longdoState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) } } diff --git a/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterMapComponent.swift index 3c197a4..9e19acd 100644 --- a/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterMapComponent.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import MapConductorMarkerCluster import MapKit import MapLibre @@ -16,16 +21,20 @@ struct PostOfficeClusterMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let markers: [MarkerState] let selectedMarker: MarkerState? + let debugHullPolygons: Bool let onMapClick: (GeoPoint) -> Void let onInfoClick: ((PostOffice) -> Void)? - @State private var googleClusterStrategy: MarkerClusterStrategy - @State private var mapLibreClusterStrategy: MarkerClusterStrategy - @State private var mapKitClusterStrategy: MarkerClusterStrategy - @State private var mapboxClusterStrategy: MarkerClusterStrategy + @StateObject private var groupState: MarkerClusterGroupState + @StateObject private var probe: SpiderfyProbe init( provider: Binding, @@ -33,8 +42,14 @@ struct PostOfficeClusterMapComponent: View { mapLibreState: MapLibreViewState, mapKitState: MapKitViewState, mapboxState: MapboxViewState, + arcGISState: ArcGISMapViewState, + hereState: HereMapViewState, + tomTomState: TomTomMapViewState, + mapTilerState: MapTilerViewState, + longdoState: LongdoViewState, markers: [MarkerState], selectedMarker: MarkerState?, + debugHullPolygons: Bool, onMapClick: @escaping (GeoPoint) -> Void, onInfoClick: ((PostOffice) -> Void)? = nil ) { @@ -43,41 +58,55 @@ struct PostOfficeClusterMapComponent: View { self.mapLibreState = mapLibreState self.mapKitState = mapKitState self.mapboxState = mapboxState + self.arcGISState = arcGISState + self.hereState = hereState + self.tomTomState = tomTomState + self.mapTilerState = mapTilerState + self.longdoState = longdoState self.markers = markers self.selectedMarker = selectedMarker + self.debugHullPolygons = debugHullPolygons self.onMapClick = onMapClick self.onInfoClick = onInfoClick - // Android default is 90 physical pixels. iOS projection uses UIKit points, - // so divide by screen scale to get the equivalent physical pixel coverage. - let radiusPt = 90.0 / UIScreen.main.scale + // Android default is 90 DIP. + let radiusPt = 75 * UIScreen.main.scale - self._googleClusterStrategy = State( - initialValue: MarkerClusterStrategy( - clusterRadiusPx: radiusPt, - enableZoomAnimation: true, - enablePanAnimation: true - ) - ) - self._mapLibreClusterStrategy = State( - initialValue: MarkerClusterStrategy( - clusterRadiusPx: radiusPt, - enableZoomAnimation: true, - enablePanAnimation: true - ) - ) - self._mapKitClusterStrategy = State( - initialValue: MarkerClusterStrategy( - clusterRadiusPx: radiusPt, - enableZoomAnimation: true, - enablePanAnimation: true - ) - ) - self._mapboxClusterStrategy = State( - initialValue: MarkerClusterStrategy( + let clusterIconImage: UIImage? = { + guard let url = Bundle.main.url(forResource: "cluster_red", withExtension: "png"), + let data = try? Data(contentsOf: url), + let image = UIImage(data: data) else { + return nil + } + return image + }() + let clusterIconProvider: (Int) -> MarkerIconProtocol = { count -> MarkerIconProtocol in + if let image = clusterIconImage { + return Self.makeClusterIcon(backgroundImage: image, count: count) + } + return DefaultMarkerIcon(label: String(count)) + } + + let probe = SpiderfyProbe() + self._probe = StateObject(wrappedValue: probe) + self._groupState = StateObject( + wrappedValue: MarkerClusterGroupState( clusterRadiusPx: radiusPt, + minClusterSize: 3, + clusterIconProvider: clusterIconProvider, + onClusterClick: { _ in + DispatchQueue.main.async { probe.clusterClicks += 1 } + }, enableZoomAnimation: true, - enablePanAnimation: true + enablePanAnimation: true, + debugHullPolygons: debugHullPolygons, + prepareExpand: { appearing in + await MainActor.run { probe.prepareCount += appearing.count } + }, + spiderfyMinZoom: 10.0, + onSpiderfyChange: { on in + DispatchQueue.main.async { probe.spiderfyOn = on } + } ) ) } @@ -89,11 +118,12 @@ struct PostOfficeClusterMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - onMapClick: onMapClick, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onMapClick: onMapClick ) { clusterLayer() @@ -103,35 +133,195 @@ struct PostOfficeClusterMapComponent: View { } } } + .onAppear { + groupState.debugHullPolygons = debugHullPolygons + } + .overlay(alignment: .bottomTrailing) { + VStack(alignment: .trailing, spacing: 2) { + Text(probe.spiderfyOn ? "SPIDERFY_ON" : "SPIDERFY_OFF") + .accessibilityIdentifier("spiderfyStatus") + Text("prepare:\(probe.prepareCount)") + .accessibilityIdentifier("prepareExpandStatus") + Text("click:\(probe.clusterClicks)") + .accessibilityIdentifier("clusterClickStatus") + } + .font(.caption2) + .foregroundColor(.white) + .padding(6) + .background(Color.black.opacity(0.6)) + .cornerRadius(6) + .padding(.trailing, 8) + .padding(.bottom, 48) + } + // The onChange closure captures pre-change view state, so read the toggle + // value from the closure parameter — reading self.debugHullPolygons here + // applies the previous value and inverts the switch behavior. + .onChange(of: debugHullPolygons) { newValue in + groupState.debugHullPolygons = newValue + } } - // ここが「クロージャで分岐」(ただし AnyView ではなく MapViewContentBuilder の世界) @MapViewContentBuilder private func clusterLayer() -> MapViewContent { if provider == .googleMaps { - MarkerClusterGroup(strategy: googleClusterStrategy) { + MarkerClusterGroup(state: groupState) { markerItems() } } else if provider == .mapKit { - MarkerClusterGroup(strategy: mapKitClusterStrategy) { + MarkerClusterGroup(state: groupState) { markerItems() } } else if provider == .mapLibre { - MarkerClusterGroup(strategy: mapLibreClusterStrategy) { + MarkerClusterGroup(state: groupState) { + markerItems() + } + } else if provider == .mapTiler { + MarkerClusterGroup(state: groupState) { + markerItems() + } + } else if provider == .longdo { + MarkerClusterGroup(state: groupState) { markerItems() } } else if provider == .mapbox { - MarkerClusterGroup(strategy: mapboxClusterStrategy) { + MarkerClusterGroup(state: groupState) { + markerItems() + } + } else if provider == .here { + MarkerClusterGroup(state: groupState) { + markerItems() + } + } else if provider == .arcGIS || provider == .arcGIS2D { + // 2D(MapView)と 3D(SceneView)は同じ Graphic をネイティブマーカーに使う。 + MarkerClusterGroup(state: groupState) { + markerItems() + } + } else if provider == .tomTom { + MarkerClusterGroup(state: groupState) { markerItems() } } } - // Marker の for ループを 1 箇所に集約 @MapViewContentBuilder private func markerItems() -> MapViewContent { for markerState in markers { Marker(state: markerState) } } + + private static let clusterIconSize = DefaultMarkerIcon.defaultIconSize + private static let clusterLabelFontSize: CGFloat = 16.0 + private static let clusterLabelRect = CGRect(x: 0.03, y: 0.04, width: 0.94, height: 0.32) + private static let clusterIconCache = ClusterIconLRUCache(capacity: 128) + + private static func makeClusterIcon(backgroundImage: UIImage, count: Int) -> BitmapIcon { + let labelText = clusterCountLabel(for: count) + if let cachedIcon = clusterIconCache.value(forKey: labelText) { + return cachedIcon + } + + let size = CGSize(width: clusterIconSize, height: clusterIconSize) + let labelRect = CGRect( + x: size.width * clusterLabelRect.minX, + y: size.height * clusterLabelRect.minY, + width: size.width * clusterLabelRect.width, + height: size.height * clusterLabelRect.height + ) + let format = UIGraphicsImageRendererFormat.default() + format.scale = UIScreen.main.scale + + let image = UIGraphicsImageRenderer(size: size, format: format).image { _ in + backgroundImage.draw(in: CGRect(origin: .zero, size: size)) + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .center + paragraphStyle.lineBreakMode = .byClipping + + let attributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.boldSystemFont(ofSize: clusterLabelFontSize), + .foregroundColor: UIColor.white, + .paragraphStyle: paragraphStyle + ] + let label = labelText as NSString + let labelSize = label.size(withAttributes: attributes) + let textRect = CGRect( + x: labelRect.minX, + y: labelRect.midY - labelSize.height / 2.0, + width: labelRect.width, + height: labelSize.height + ) + label.draw(in: textRect, withAttributes: attributes) + } + + let icon = BitmapIcon( + bitmap: image, + anchor: CGPoint(x: 0.5, y: 0.5), + size: size, + iconSize: clusterIconSize, + infoAnchor: CGPoint(x: 0.5, y: 0.5) + ) + clusterIconCache.insert(icon, forKey: labelText) + return icon + } + + private static func clusterCountLabel(for count: Int) -> String { + if count > 1_000 { + return "1k+" + } + if count > 200 { + return "200+" + } + if count > 100 { + return "100+" + } + return String(count) + } + + private final class ClusterIconLRUCache { + private let capacity: Int + private var storage: [String: BitmapIcon] = [:] + private var recentlyUsedKeys: [String] = [] + private let lock = NSLock() + + init(capacity: Int) { + self.capacity = max(1, capacity) + } + + func value(forKey key: String) -> BitmapIcon? { + lock.lock() + defer { lock.unlock() } + + guard let value = storage[key] else { + return nil + } + markRecentlyUsed(key) + return value + } + + func insert(_ value: BitmapIcon, forKey key: String) { + lock.lock() + defer { lock.unlock() } + + storage[key] = value + markRecentlyUsed(key) + while recentlyUsedKeys.count > capacity, let leastRecentlyUsedKey = recentlyUsedKeys.first { + recentlyUsedKeys.removeFirst() + storage.removeValue(forKey: leastRecentlyUsedKey) + } + } + + private func markRecentlyUsed(_ key: String) { + recentlyUsedKeys.removeAll { $0 == key } + recentlyUsedKeys.append(key) + } + } +} + +/// Verification-only bridge: surfaces spiderfy/prepareExpand/cluster-click +/// activity to the UI overlay so the XCUITest can assert on it. +final class SpiderfyProbe: ObservableObject { + @Published var spiderfyOn = false + @Published var prepareCount = 0 + @Published var clusterClicks = 0 } diff --git a/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterMapPage.swift index 4fd1772..c100e2d 100644 --- a/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterMapPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct PostOfficeClusterMapPage: View { @@ -15,6 +20,11 @@ struct PostOfficeClusterMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -35,6 +45,26 @@ struct PostOfficeClusterMapPage: View { _mapboxState = StateObject(wrappedValue: MapboxViewState( cameraPosition: vm.initCameraPosition )) + _arcGISState = StateObject(wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + )) + _hereState = StateObject(wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + )) + _tomTomState = StateObject(wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + )) + _mapTilerState = StateObject(wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + )) + _longdoState = StateObject(wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + )) } var body: some View { @@ -46,8 +76,14 @@ struct PostOfficeClusterMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, markers: viewModel.markers, selectedMarker: viewModel.selectedMarker, + debugHullPolygons: viewModel.debugHullPolygons, onMapClick: { _ in viewModel.clearSelection() }, @@ -56,6 +92,24 @@ struct PostOfficeClusterMapPage: View { } ) + VStack(alignment: .leading, spacing: 12) { + Text("Controls") + .font(.headline) + .foregroundColor(.primary) + + Toggle(isOn: $viewModel.debugHullPolygons) { + Text("debug") + .font(.subheadline) + } + } + .padding(16) + .background(Color(UIColor.systemBackground).opacity(0.95)) + .cornerRadius(12) + .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) + .padding(.leading, 16) + .padding(.bottom, 16) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading) + if viewModel.isDataLoading { LoadingOverlay( title: "Loading Post Offices", @@ -93,6 +147,16 @@ struct PostOfficeClusterMapPage: View { mapKitState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) case .mapbox: mapboxState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) + case .arcGIS, .arcGIS2D: + arcGISState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) + case .here: + hereState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) + case .tomTom: + tomTomState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) + case .mapTiler: + mapTilerState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) + case .longdo: + longdoState.moveCameraTo(cameraPosition: camera, durationMillis: 2000) } } @@ -112,6 +176,16 @@ struct PostOfficeClusterMapPage: View { mapKitState.moveCameraTo(cameraPosition: mapKitState.cameraPosition, durationMillis: 0) case .mapbox: mapboxState.moveCameraTo(cameraPosition: mapboxState.cameraPosition, durationMillis: 0) + case .arcGIS, .arcGIS2D: + arcGISState.moveCameraTo(cameraPosition: arcGISState.cameraPosition, durationMillis: 0) + case .here: + hereState.moveCameraTo(cameraPosition: hereState.cameraPosition, durationMillis: 0) + case .tomTom: + tomTomState.moveCameraTo(cameraPosition: tomTomState.cameraPosition, durationMillis: 0) + case .mapTiler: + mapTilerState.moveCameraTo(cameraPosition: mapTilerState.cameraPosition, durationMillis: 0) + case .longdo: + longdoState.moveCameraTo(cameraPosition: longdoState.cameraPosition, durationMillis: 0) } } diff --git a/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterPageViewModel.swift index cfa5ae8..a9b350b 100644 --- a/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterPageViewModel.swift +++ b/samples/MapConductorSampleApp/Sources/pages/marker/postofficecluster/PostOfficeClusterPageViewModel.swift @@ -10,6 +10,7 @@ final class PostOfficeClusterPageViewModel: ObservableObject { @Published var markers: [MarkerState] = [] @Published var selectedMarker: MarkerState? @Published var isDataLoading: Bool = false + @Published var debugHullPolygons: Bool = false init(postOfficeIcon: ImageIcon) { self.initCameraPosition = MapCameraPosition( diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapComponent.swift index 14f0a04..666eea3 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapComponent.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForLongdo import SwiftUI import UIKit @@ -13,6 +18,11 @@ struct PolygonMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let polygonState: PolygonState let polygonVertexMarkers: [MarkerState] @@ -24,10 +34,11 @@ struct PolygonMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapPage.swift index 03918b2..3b8c10d 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct PolygonMapPage: View { @@ -15,6 +20,11 @@ struct PolygonMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -39,6 +49,36 @@ struct PolygonMapPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -50,6 +90,11 @@ struct PolygonMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, polygonState: viewModel.polygonState, polygonVertexMarkers: viewModel.polygonVertexMarkers ) diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapPageViewModel.swift index 684b694..597df20 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapPageViewModel.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/basic/PolygonMapPageViewModel.swift @@ -57,7 +57,7 @@ final class PolygonMapPageViewModel: ObservableObject { icon: DefaultMarkerIcon( fillColor: UIColor.yellow, strokeColor: UIColor.black, - scale: 0.7, + scale: 0.7 ), draggable: true, onDrag: nil diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/click/PolygonClickMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/click/PolygonClickMapPage.swift index 0a79f96..1bf0ae3 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polygon/click/PolygonClickMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/click/PolygonClickMapPage.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForLongdo import SwiftUI import UIKit @@ -17,6 +22,11 @@ struct PolygonClickMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -41,6 +51,36 @@ struct PolygonClickMapPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -52,11 +92,12 @@ struct PolygonClickMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - onMapClick: viewModel.onMapClicked, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + onMapClick: viewModel.onMapClicked ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/click/PolygonClickPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/click/PolygonClickPageViewModel.swift index 4578298..b331804 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polygon/click/PolygonClickPageViewModel.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/click/PolygonClickPageViewModel.swift @@ -21,7 +21,7 @@ final class PolygonClickPageViewModel: ObservableObject { message = "Outside" markerState = MarkerState( position: clicked, - id: "clicked", + id: "clicked" ) } @@ -30,7 +30,7 @@ final class PolygonClickPageViewModel: ObservableObject { message = "Inside\n\(latLng)" markerState = MarkerState( position: GeoPoint.from(position: event.clicked), - id: "clicked", + id: "clicked" ) } } diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/geodesic/PolygonGeodesicPage.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/geodesic/PolygonGeodesicPage.swift index 24cba4e..13cc0bd 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polygon/geodesic/PolygonGeodesicPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/geodesic/PolygonGeodesicPage.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForLongdo import SwiftUI import UIKit @@ -17,6 +22,11 @@ struct PolygonGeodesicPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -41,6 +51,36 @@ struct PolygonGeodesicPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -52,10 +92,11 @@ struct PolygonGeodesicPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/hole/HolePolygonMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/hole/HolePolygonMapPage.swift new file mode 100644 index 0000000..997cb1b --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/hole/HolePolygonMapPage.swift @@ -0,0 +1,126 @@ +import MapConductorCore +import MapConductorForLongdo +import MapConductorForArcGIS +import MapConductorForGoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForMapKit +import MapConductorForMapLibre +import MapConductorForMapbox +import SwiftUI + +struct HolePolygonMapPage: View { + let onToggleSidebar: () -> Void + + @State private var provider: MapProvider + @StateObject private var viewModel: HolePolygonMapPageViewModel + + @StateObject private var googleState: GoogleMapViewState + @StateObject private var mapLibreState: MapLibreViewState + @StateObject private var mapKitState: MapKitViewState + @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState + + init(onToggleSidebar: @escaping () -> Void = {}) { + self.onToggleSidebar = onToggleSidebar + let vm = HolePolygonMapPageViewModel() + _viewModel = StateObject(wrappedValue: vm) + _provider = State(initialValue: MapProvider.initial()) + _googleState = StateObject(wrappedValue: GoogleMapViewState(cameraPosition: vm.initCameraPosition)) + _mapLibreState = StateObject( + wrappedValue: MapLibreViewState( + mapDesignType: MapLibreDesign.DemoTiles, + cameraPosition: vm.initCameraPosition + ) + ) + _mapKitState = StateObject( + wrappedValue: MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapboxState = StateObject(wrappedValue: MapboxViewState(cameraPosition: vm.initCameraPosition)) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) + } + + var body: some View { + DemoMapPageScaffold(provider: $provider, onToggleSidebar: onToggleSidebar) { + ZStack(alignment: .bottomLeading) { + SampleMapView( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + ) { + { () -> MapViewContent in + var content = MapViewContent() + content.polygons = [Polygon(state: viewModel.polygonState)] + content.markers = viewModel.holeVertexMarkers.map { Marker(state: $0) } + return content + }() + } + + VStack(alignment: .leading, spacing: 8) { + Text("Hole Polygon Example") + .font(.headline) + .foregroundColor(.primary) + + Text( + """ + A regional polygon covering the Sapporo area with two triangular holes. + Drag hole vertex markers to reshape the holes. + """ + ) + .font(.subheadline) + .foregroundColor(.secondary) + } + .padding(16) + .background(Color(UIColor.systemBackground).opacity(0.95)) + .cornerRadius(12) + .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) + .padding(.leading, 16) + .padding(.bottom, 16) + } + } + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/hole/HolePolygonMapPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/hole/HolePolygonMapPageViewModel.swift new file mode 100644 index 0000000..16aa575 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/hole/HolePolygonMapPageViewModel.swift @@ -0,0 +1,149 @@ +import Foundation +import MapConductorCore +import UIKit + +final class HolePolygonMapPageViewModel: ObservableObject { + let initCameraPosition: MapCameraPosition + let polygonState: PolygonState + let holeVertexMarkers: [MarkerState] + + private var holes: [[GeoPoint]] + + init() { + self.initCameraPosition = MapCameraPosition( + position: GeoPoint(latitude: 43.06050568387817, longitude: 141.35374551567804), + zoom: 11.0, + bearing: 0.0, + tilt: 0.0, + paddings: nil + ) + + // Android の HolePolygonMapPage と同じく、札幌周辺だけを覆う外周を使う。 + // 世界外周を 10 km 間隔で補間すると、hole のドラッグごとに数千頂点の + // ArcGIS geometry を再構築することになり、ドラッグへの追従が遅れる。 + let outerPoints: [GeoPoint] = [ + GeoPoint(latitude: 44.2, longitude: 140.0), + GeoPoint(latitude: 44.2, longitude: 142.8), + GeoPoint(latitude: 42.0, longitude: 142.8), + GeoPoint(latitude: 42.0, longitude: 140.0) + ] + + self.holes = [ + [ + GeoPoint(latitude: 43.10086924222251, longitude: 141.35290903949243), + GeoPoint(latitude: 43.04444342582366, longitude: 141.4118953480885), + GeoPoint(latitude: 43.05060149394299, longitude: 141.30656265416695) + ], + [ + GeoPoint(latitude: 43.06035050410283, longitude: 141.31990479539704), + GeoPoint(latitude: 43.038284739487004, longitude: 141.33324693662706), + GeoPoint(latitude: 43.049062034871525, longitude: 141.28690055130158) + ] + ] + + self.polygonState = PolygonState( + points: outerPoints, + holes: holes, + id: "sapporo-hole", + strokeColor: .red, + strokeWidth: 2.0, + fillColor: UIColor(red: 120.0 / 255.0, green: 120.0 / 255.0, blue: 128.0 / 255.0, alpha: 0.8), + geodesic: false + ) + + let holeMarkerColors: [UIColor] = [ + UIColor(red: 0x25 / 255.0, green: 0x63 / 255.0, blue: 0xEB / 255.0, alpha: 1.0), + UIColor(red: 0xF9 / 255.0, green: 0x73 / 255.0, blue: 0x16 / 255.0, alpha: 1.0) + ] + let defaultHoleMarkerColor = UIColor(red: 0x64 / 255.0, green: 0x74 / 255.0, blue: 0x8B / 255.0, alpha: 1.0) + + var markers: [MarkerState] = [] + for (holeIndex, hole) in holes.enumerated() { + for (vertexIndex, point) in hole.enumerated() { + markers.append( + MarkerState( + position: point, + id: "hole-\(holeIndex)-\(vertexIndex)", + extra: HoleVertex(holeIndex: holeIndex, vertexIndex: vertexIndex), + icon: DefaultMarkerIcon( + fillColor: holeIndex < holeMarkerColors.count ? holeMarkerColors[holeIndex] : defaultHoleMarkerColor, + strokeColor: .white, + label: "\(holeIndex + 1)-\(vertexIndex + 1)", + labelTextColor: .white + ), + clickable: false, + draggable: true, + onDrag: nil + ) + ) + } + } + + self.holeVertexMarkers = markers + self.holeVertexMarkers.forEach { marker in + marker.onDrag = { [weak self] dragged in + self?.onMarkerDrag(dragged) + } + marker.onDragEnd = { [weak self] dragged in + self?.onMarkerDrag(dragged) + } + } + + if ProcessInfo.processInfo.environment["MAPCONDUCTOR_SAMPLE_HOLE_AUTODRIFT"] == "1" { + startAutoDrift() + } + } + + deinit { + driftTimer?.invalidate() + } + + /// テスト用: 環境変数 MAPCONDUCTOR_SAMPLE_HOLE_AUTODRIFT=1 のとき、穴 0 の三角形を + /// 東西に往復移動させる(頂点ドラッグと同じ「state の in-place 変更」経路を通す)。 + /// UI テストがスクリーンショット比較で「穴が動いて再描画されるか」を機械検証するために使う。 + private var driftTimer: Timer? + private var driftTick = 0 + private var baseHole0: [GeoPoint] = [] + + private func startAutoDrift() { + NSLog("[MapConductor][Sample] hole autodrift starting") + baseHole0 = holes[0] + driftTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in + guard let self else { + NSLog("[MapConductor][Sample] hole autodrift tick skipped (view model gone)") + return + } + self.driftTick += 1 + NSLog("[MapConductor][Sample] hole autodrift tick=%d", self.driftTick) + // 単調に東へ移動(スクリーンショット間隔と往復周期のエイリアシングを避ける)。 + // 1 tick あたり 0.008°(ズーム 11 で約 12px)。 + let offset = 0.008 * Double(self.driftTick) + self.holes[0] = self.baseHole0.map { + GeoPoint(latitude: $0.latitude, longitude: $0.longitude + offset) + } + self.polygonState.holes = self.holes + // マーカーもドラッグ相当に追従させる(marker update 経路のちらつき検証用)。 + for (vertexIndex, point) in self.holes[0].enumerated() { + if let marker = self.holeVertexMarkers.first(where: { $0.id == "hole-0-\(vertexIndex)" }) { + marker.position = point + } + } + } + } + + func onMarkerDrag(_ dragged: MarkerState) { + guard let vertex = dragged.extra as? HoleVertex else { return } + guard vertex.holeIndex >= 0 && vertex.holeIndex < holes.count else { return } + + let hole = holes[vertex.holeIndex] + guard vertex.vertexIndex >= 0 && vertex.vertexIndex < hole.count else { return } + + holes[vertex.holeIndex][vertex.vertexIndex] = GeoPoint.from(position: dragged.position) + polygonState.holes = holes + } + + private struct HoleVertex { + let holeIndex: Int + let vertexIndex: Int + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapComponent.swift index 14f0a04..6c61c7c 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapComponent.swift @@ -1,9 +1,12 @@ -import GoogleMaps import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import SwiftUI import UIKit @@ -13,6 +16,10 @@ struct PolygonMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState let polygonState: PolygonState let polygonVertexMarkers: [MarkerState] @@ -24,10 +31,10 @@ struct PolygonMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapPage.swift index 03918b2..fdff9dd 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapPage.swift @@ -3,6 +3,10 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import SwiftUI struct PolygonMapPage: View { @@ -15,6 +19,10 @@ struct PolygonMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -39,6 +47,30 @@ struct PolygonMapPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -50,6 +82,10 @@ struct PolygonMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, polygonState: viewModel.polygonState, polygonVertexMarkers: viewModel.polygonVertexMarkers ) diff --git a/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapPageViewModel.swift index 684b694..597df20 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapPageViewModel.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polygon/holes/PolygonMapPageViewModel.swift @@ -57,7 +57,7 @@ final class PolygonMapPageViewModel: ObservableObject { icon: DefaultMarkerIcon( fillColor: UIColor.yellow, strokeColor: UIColor.black, - scale: 0.7, + scale: 0.7 ), draggable: true, onDrag: nil diff --git a/samples/MapConductorSampleApp/Sources/pages/polyline/basic/PolylineMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/polyline/basic/PolylineMapComponent.swift index aebbb48..9bcdadc 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polyline/basic/PolylineMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polyline/basic/PolylineMapComponent.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForLongdo import SwiftUI import UIKit @@ -13,6 +18,11 @@ struct PolylineMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let polylineState: PolylineState let wayPointMarkers: [MarkerState] @@ -24,10 +34,11 @@ struct PolylineMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/polyline/basic/PolylineMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/polyline/basic/PolylineMapPage.swift index 3a7d21b..f6796b6 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polyline/basic/PolylineMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polyline/basic/PolylineMapPage.swift @@ -1,8 +1,13 @@ import MapConductorCore +import MapConductorForLongdo import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import SwiftUI struct PolylineMapPage: View { @@ -15,6 +20,11 @@ struct PolylineMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -39,6 +49,36 @@ struct PolylineMapPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -49,6 +89,11 @@ struct PolylineMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, polylineState: viewModel.polylineState, wayPointMarkers: viewModel.wayPointMarkers ) diff --git a/samples/MapConductorSampleApp/Sources/pages/polyline/click/PolylineClickMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/polyline/click/PolylineClickMapComponent.swift index a81bb7e..e694133 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polyline/click/PolylineClickMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polyline/click/PolylineClickMapComponent.swift @@ -1,9 +1,14 @@ import GoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler import MapConductorCore import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForLongdo import SwiftUI import UIKit @@ -13,6 +18,11 @@ struct PolylineClickMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let polylineState: PolylineState let markers: [MarkerState] @@ -24,10 +34,11 @@ struct PolylineClickMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/polyline/click/PolylineClickMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/polyline/click/PolylineClickMapPage.swift index 31ebfaa..9b4c72c 100644 --- a/samples/MapConductorSampleApp/Sources/pages/polyline/click/PolylineClickMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/polyline/click/PolylineClickMapPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct PolylineClickMapPage: View { @@ -15,6 +20,11 @@ struct PolylineClickMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -38,6 +48,36 @@ struct PolylineClickMapPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -49,6 +89,11 @@ struct PolylineClickMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, polylineState: viewModel.polylineState, markers: viewModel.markers ) diff --git a/samples/MapConductorSampleApp/Sources/pages/polyline/geodesic/PolylineGeodesicPage.swift b/samples/MapConductorSampleApp/Sources/pages/polyline/geodesic/PolylineGeodesicPage.swift new file mode 100644 index 0000000..3218766 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/polyline/geodesic/PolylineGeodesicPage.swift @@ -0,0 +1,163 @@ +import GoogleMaps +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorCore +import MapConductorForGoogleMaps +import MapConductorForMapLibre +import MapConductorForMapKit +import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForLongdo +import SwiftUI +import UIKit + +struct PolylineGeodesicPage: View { + let onToggleSidebar: () -> Void + + @State private var provider: MapProvider + @StateObject private var viewModel: PolylineGeodesicPageViewModel + + @StateObject private var googleState: GoogleMapViewState + @StateObject private var mapLibreState: MapLibreViewState + @StateObject private var mapKitState: MapKitViewState + @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState + + init(onToggleSidebar: @escaping () -> Void = {}) { + self.onToggleSidebar = onToggleSidebar + let vm = PolylineGeodesicPageViewModel() + _viewModel = StateObject(wrappedValue: vm) + _provider = State(initialValue: MapProvider.initial()) + _googleState = StateObject(wrappedValue: GoogleMapViewState(cameraPosition: vm.initCameraPosition)) + _mapLibreState = StateObject( + wrappedValue: MapLibreViewState( + mapDesignType: MapLibreDesign.DemoTiles, + cameraPosition: vm.initCameraPosition + ) + ) + _mapKitState = StateObject( + wrappedValue: MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapboxState = StateObject( + wrappedValue: MapboxViewState( + cameraPosition: vm.initCameraPosition + ) + ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) + } + + var body: some View { + DemoMapPageScaffold(provider: $provider, onToggleSidebar: onToggleSidebar) { + ZStack(alignment: .bottomLeading) { + SampleMapView( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState + ) { + { () -> MapViewContent in + var content = MapViewContent() + let points: [GeoPointProtocol] = [ + GeoPoint.fromLongLat(longitude: 23.66, latitude: 56.42), + GeoPoint.fromLongLat(longitude: 13.39, latitude: 2.95), + GeoPoint.fromLongLat(longitude: -87.82, latitude: 38.58), + GeoPoint.fromLongLat(longitude: 23.66, latitude: 56.42) + ] + let basePolygon = PolygonState( + points: points, + strokeColor: UIColor.yellow.withAlphaComponent(0.3), + strokeWidth: 3.0, + fillColor: UIColor.green.withAlphaComponent(0.5), + geodesic: false, + zIndex: 0, + onClick: viewModel.onPolygonClicked + ) + let geodesicPolygon = PolygonState( + points: points, + strokeColor: UIColor.red.withAlphaComponent(0.3), + strokeWidth: 3.0, + fillColor: UIColor.blue.withAlphaComponent(0.5), + geodesic: true, + zIndex: 1, + onClick: viewModel.onPolygonClicked + ) + content.polygons = [ + Polygon(state: basePolygon), + Polygon(state: geodesicPolygon) + ] + if let marker = viewModel.markerState { + content.markers = [Marker(state: marker)] + } + return content + }() + } + + VStack(alignment: .leading, spacing: 8) { + Text("Polygon Geodesic Example") + .font(.headline) + .foregroundColor(.primary) + + Text( + """ + Tap on the polygons! + This example shows the ability of the polygon click detection. + Place a green marker if you tap on the green polygon, + and place a blue marker on the blue polygon if you tap on it. + """ + ) + .font(.subheadline) + .foregroundColor(.secondary) + } + .padding(16) + .background(Color(UIColor.systemBackground).opacity(0.95)) + .cornerRadius(12) + .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) + .padding(.leading, 16) + .padding(.bottom, 16) + } + } + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/polyline/geodesic/PolylineGeodesicPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/polyline/geodesic/PolylineGeodesicPageViewModel.swift new file mode 100644 index 0000000..776069d --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/polyline/geodesic/PolylineGeodesicPageViewModel.swift @@ -0,0 +1,28 @@ +import Foundation +import MapConductorCore +import UIKit + +final class PolylineGeodesicPageViewModel: ObservableObject { + let initCameraPosition: MapCameraPosition + + @Published private(set) var markerState: MarkerState? + + init() { + self.initCameraPosition = MapCameraPosition( + position: GeoPoint(latitude: 30.0, longitude: 0.0), + zoom: 1.0, + bearing: 0.0, + tilt: 0.0, + paddings: nil + ) + } + + func onPolygonClicked(_ event: PolygonEvent) { + let color = event.state.fillColor.withAlphaComponent(1.0) + markerState = MarkerState( + position: GeoPoint.from(position: event.clicked), + id: "clicked", + icon: DefaultMarkerIcon(fillColor: color) + ) + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/raster/RasterHeaderPage.swift b/samples/MapConductorSampleApp/Sources/pages/raster/RasterHeaderPage.swift new file mode 100644 index 0000000..e791122 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/pages/raster/RasterHeaderPage.swift @@ -0,0 +1,129 @@ +import MapConductorCore +import MapConductorForArcGIS +import MapConductorForGoogleMaps +import MapConductorForHERE +import MapConductorForLongdo +import MapConductorForMapKit +import MapConductorForMapLibre +import MapConductorForMapTiler +import MapConductorForMapbox +import MapConductorForTomTom +import SwiftUI + +/// `RasterHeaderUITests` が使う計測ページ。 +/// +/// `RasterLayerState` の `userAgent` / `extraHeaders` が**実際にタイル要求に載るか**を +/// プロバイダごとに確かめる。タイルの取得はプロバイダのネイティブ SDK が握っているので、 +/// 送り出す側のコードを読んでも「載っているか」は分からない。受け取る側をアプリ内に立てて +/// 届いたヘッダをそのまま読む(``HeaderRecordingTileServer``)。 +/// +/// プロバイダは `MAPCONDUCTOR_SAMPLE_PROVIDER` で選ぶ。 +/// +/// アクセシビリティ経由で次を公開する: +/// +/// - `rasterHeaderCount` 受け取ったタイル要求の数。0 ならそもそも取りに来ていない +/// - `rasterHeaderUA` 観測した User-Agent(未観測は `-`) +/// - `rasterHeaderCustom` 観測した `X-MapConductor-Test`(未観測は `-`) +/// +/// `rasterHeaderCount` を分けているのは、失敗したときに**取りに来ていない**のか +/// **来たがヘッダが無い**のかを区別するため。前者はページの不備、後者が本題。 +struct RasterHeaderPage: View { + /// 送信を期待する値。UI テスト側と一致させること。 + static let expectedUserAgent = "MapConductorRasterHeaderProbe/1.0" + static let expectedHeaderName = "X-MapConductor-Test" + static let expectedHeaderValue = "mapconductor-probe" + + private static let center = GeoPoint(latitude: 35.681236, longitude: 139.767125) + private static let camera = MapCameraPosition(position: RasterHeaderPage.center, zoom: 8.0) + + let onToggleSidebar: () -> Void + + @StateObject private var probe = RasterHeaderProbe() + @State private var provider: MapProvider = MapProvider.initial(fallback: .mapLibre) + + @StateObject private var googleState = GoogleMapViewState(cameraPosition: RasterHeaderPage.camera) + @StateObject private var mapLibreState = MapLibreViewState( + mapDesignType: MapLibreDesign.DemoTiles, cameraPosition: RasterHeaderPage.camera) + @StateObject private var mapKitState = MapKitViewState( + mapDesignType: MapKitMapDesign.Standard, cameraPosition: RasterHeaderPage.camera) + @StateObject private var mapboxState = MapboxViewState(cameraPosition: RasterHeaderPage.camera) + @StateObject private var arcGISState = ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, cameraPosition: RasterHeaderPage.camera) + @StateObject private var hereState = HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, cameraPosition: RasterHeaderPage.camera) + @StateObject private var tomTomState = TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, cameraPosition: RasterHeaderPage.camera) + @StateObject private var mapTilerState = MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, cameraPosition: RasterHeaderPage.camera) + @StateObject private var longdoState = LongdoViewState( + mapDesignType: LongdoDesign.Normal, cameraPosition: RasterHeaderPage.camera) + + private let refresh = Timer.publish(every: 0.25, on: .main, in: .common).autoconnect() + + var body: some View { + ZStack(alignment: .top) { + SampleMapView( + provider: $provider, + googleState: googleState, + mapLibreState: mapLibreState, + mapKitState: mapKitState, + mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, + sdkInitialize: SampleMapView.initializeAllSDKs + ) { + var content = MapViewContent() + content.rasterLayers = [RasterLayer(state: probe.rasterLayerState)] + return content + } + + VStack(spacing: 0) { + Text(probe.observedCount).accessibilityIdentifier("rasterHeaderCount") + Text(probe.observedUserAgent).accessibilityIdentifier("rasterHeaderUA") + Text(probe.observedCustomHeader).accessibilityIdentifier("rasterHeaderCustom") + } + .font(.caption2) + .opacity(0.85) + } + .onReceive(refresh) { _ in probe.refresh() } + } +} + +/// 計測サーバとラスタレイヤの寿命を握る。 +/// +/// `View` の struct に持たせると再構築のたびにサーバが立ち上がるので、 +/// `@StateObject` にして 1 回だけ作る。 +@MainActor +final class RasterHeaderProbe: ObservableObject { + @Published var observedCount: String = "0" + @Published var observedUserAgent: String = "-" + @Published var observedCustomHeader: String = "-" + + private let server = HeaderRecordingTileServer.start() + let rasterLayerState: RasterLayerState + + init() { + rasterLayerState = RasterLayerState( + source: .urlTemplate(template: server.urlTemplate(), tileSize: 256), + opacity: 1.0, + visible: true, + zIndex: 0, + userAgent: RasterHeaderPage.expectedUserAgent, + extraHeaders: [RasterHeaderPage.expectedHeaderName: RasterHeaderPage.expectedHeaderValue], + id: "raster_header_probe" + ) + } + + func refresh() { + observedCount = String(server.requestCount) + observedUserAgent = server.anyHeader("User-Agent") ?? "-" + observedCustomHeader = server.anyHeader(RasterHeaderPage.expectedHeaderName) ?? "-" + } + + deinit { + server.stop() + } +} diff --git a/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerMapComponent.swift b/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerMapComponent.swift index 201a1e6..1ed7065 100644 --- a/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerMapComponent.swift +++ b/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerMapComponent.swift @@ -4,6 +4,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI import UIKit @@ -13,6 +18,11 @@ struct RasterLayerMapComponent: View { @ObservedObject var mapLibreState: MapLibreViewState @ObservedObject var mapKitState: MapKitViewState @ObservedObject var mapboxState: MapboxViewState + @ObservedObject var arcGISState: ArcGISMapViewState + @ObservedObject var hereState: HereMapViewState + @ObservedObject var tomTomState: TomTomMapViewState + @ObservedObject var mapTilerState: MapTilerViewState + @ObservedObject var longdoState: LongdoViewState let rasterLayerState: RasterLayerState @@ -23,10 +33,11 @@ struct RasterLayerMapComponent: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, - sdkInitialize: { - GMSServices.provideAPIKey(SampleConfig.googleMapsApiKey) - initializeMapbox(accessToken: SampleConfig.mapboxAccessToken) - } + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState ) { { () -> MapViewContent in var content = MapViewContent() diff --git a/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerMapPage.swift b/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerMapPage.swift index eb4d6c0..56f7d21 100644 --- a/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerMapPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerMapPage.swift @@ -3,6 +3,11 @@ import MapConductorForGoogleMaps import MapConductorForMapLibre import MapConductorForMapKit import MapConductorForMapbox +import MapConductorForArcGIS +import MapConductorForHERE +import MapConductorForTomTom +import MapConductorForMapTiler +import MapConductorForLongdo import SwiftUI struct RasterLayerMapPage: View { @@ -15,6 +20,11 @@ struct RasterLayerMapPage: View { @StateObject private var mapLibreState: MapLibreViewState @StateObject private var mapKitState: MapKitViewState @StateObject private var mapboxState: MapboxViewState + @StateObject private var arcGISState: ArcGISMapViewState + @StateObject private var hereState: HereMapViewState + @StateObject private var tomTomState: TomTomMapViewState + @StateObject private var mapTilerState: MapTilerViewState + @StateObject private var longdoState: LongdoViewState init(onToggleSidebar: @escaping () -> Void = {}) { self.onToggleSidebar = onToggleSidebar @@ -39,6 +49,36 @@ struct RasterLayerMapPage: View { cameraPosition: vm.initCameraPosition ) ) + _arcGISState = StateObject( + wrappedValue: ArcGISMapViewState( + mapDesignType: ArcGISDesign.OsmStandard, + cameraPosition: vm.initCameraPosition + ) + ) + _hereState = StateObject( + wrappedValue: HereMapViewState( + mapDesignType: HereMapDesign.NormalDay, + cameraPosition: vm.initCameraPosition + ) + ) + _tomTomState = StateObject( + wrappedValue: TomTomMapViewState( + mapDesignType: TomTomMapDesign.Standard, + cameraPosition: vm.initCameraPosition + ) + ) + _mapTilerState = StateObject( + wrappedValue: MapTilerViewState( + mapDesignType: MapTilerDesign.Streets, + cameraPosition: vm.initCameraPosition + ) + ) + _longdoState = StateObject( + wrappedValue: LongdoViewState( + mapDesignType: LongdoDesign.Normal, + cameraPosition: vm.initCameraPosition + ) + ) } var body: some View { @@ -50,19 +90,72 @@ struct RasterLayerMapPage: View { mapLibreState: mapLibreState, mapKitState: mapKitState, mapboxState: mapboxState, + arcGISState: arcGISState, + hereState: hereState, + tomTomState: tomTomState, + mapTilerState: mapTilerState, + longdoState: longdoState, rasterLayerState: viewModel.rasterLayerState ) - VStack(alignment: .leading, spacing: 8) { - Text("Raster Layer") + VStack(alignment: .leading, spacing: 10) { + Text("Raster Layer Example") .font(.headline) .foregroundColor(.primary) - Text("Thunderforest Landscape tiles overlay.") - .font(.subheadline) - .foregroundColor(.secondary) + Menu { + ForEach(viewModel.availableLayers) { layer in + Button { + viewModel.selectLayer(layer) + } label: { + if layer.id == viewModel.selectedLayer.id { + Label(layer.displayName, systemImage: "checkmark") + } else { + Text(layer.displayName) + } + } + } + } label: { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text("GSI layer") + .font(.caption) + .foregroundColor(.secondary) + Text(viewModel.selectedLayer.displayName) + .font(.subheadline) + .foregroundColor(.primary) + .lineLimit(1) + } + Spacer() + Image(systemName: "chevron.down") + .foregroundColor(.secondary) + } + .padding(10) + .background(Color(UIColor.secondarySystemBackground)) + .cornerRadius(8) + } + + VStack(alignment: .leading, spacing: 6) { + if provider == .tomTom { + Text("Opacity: not available for TomTom") + } else { + Text("Opacity: \(String(format: "%.1f", viewModel.opacity))") + } + + Slider( + value: Binding( + get: { viewModel.opacity }, + set: { viewModel.setOpacity($0) } + ), + in: 0.0...1.0 + ) + .disabled(provider == .tomTom) + } + .font(.subheadline) + .foregroundColor(.primary) } .padding(16) + .frame(maxWidth: 360) .background(Color(UIColor.systemBackground).opacity(0.95)) .cornerRadius(12) .shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 4) diff --git a/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerPageViewModel.swift b/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerPageViewModel.swift index d75bab1..fe8776e 100644 --- a/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerPageViewModel.swift +++ b/samples/MapConductorSampleApp/Sources/pages/raster/RasterLayerPageViewModel.swift @@ -1,24 +1,117 @@ import Foundation import MapConductorCore +struct GsiLayer: Identifiable { + let id: String + let displayName: String + let source: RasterSource +} + +enum DefaultGsiLayers { + static let nasa = GsiLayer( + id: "nasa", + displayName: "Relief map", + source: .urlTemplate( + template: "https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/" + + "MODIS_Terra_CorrectedReflectance_TrueColor/default/2024-01-01/" + + "GoogleMapsCompatible_Level9/{z}/{y}/{x}.jpg", + tileSize: 256, + minZoom: 5, + maxZoom: 15, + attributionRules: [ + AttributionRule( + attribution: "NASA Global Imagery Browse Services(GIBS / EOSDIS)" + ) + ] + ) + ) + + static let standard = GsiLayer( + id: "standard", + displayName: "Standard map (電子国土基本図)", + source: .urlTemplate( + template: "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png", + tileSize: 256, + minZoom: 5, + maxZoom: 18, + attributionRules: [ + AttributionRule( + attribution: "地理院タイル" + ), + AttributionRule( + attribution: "The bathymetric contours are derived from those contained within " + + "the GEBCO Digital Atlas, published by the BODC on behalf of IOC and IHO " + + "(2003) (https://www.gebco.net)", + minZoom: 5, + maxZoom: 8 + ), + AttributionRule( + attribution: "海上保安庁許可第292502号(水路業務法第25条に基づく類似刊行物)", + minZoom: 5, + maxZoom: 8 + ), + AttributionRule( + attribution: "Shoreline data is derived from: United States. National Imagery " + + "and Mapping Agency. "Vector Map Level 0 (VMAP0)." Bethesda, MD: " + + "Denver, CO: The Agency; USGS Information Services, 1997.", + minZoom: 5, + maxZoom: 8 + ) + ] + ) + ) + + static let all = [nasa, standard] +} + final class RasterLayerPageViewModel: ObservableObject { let initCameraPosition: MapCameraPosition + let availableLayers: [GsiLayer] let rasterLayerState: RasterLayerState - init() { - let center = GeoPoint(latitude: 35.681236, longitude: 139.767125) + @Published private(set) var opacity: Double + @Published private(set) var selectedLayer: GsiLayer + + init( + layers: [GsiLayer] = DefaultGsiLayers.all, + initialLayer: GsiLayer = DefaultGsiLayers.nasa + ) { + precondition(!layers.isEmpty, "At least one GSI layer must be injected") + precondition(Set(layers.map(\.id)).count == layers.count, "Injected GSI layer IDs must be unique") + precondition(layers.contains { $0.id == initialLayer.id }, "The initial GSI layer must be included") + + let selectedLayer = layers.first { $0.id == initialLayer.id }! + let initialOpacity = 0.75 + + self.availableLayers = layers + self.selectedLayer = selectedLayer + self.opacity = initialOpacity self.initCameraPosition = MapCameraPosition( - position: center, - zoom: 11.0, + position: GeoPoint(latitude: 35.6812, longitude: 139.7671), + zoom: 7.0, bearing: 0.0, tilt: 0.0, paddings: nil ) - - let source = RasterSource.urlTemplate( - template: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", - tileSize: 256 + self.rasterLayerState = RasterLayerState( + source: selectedLayer.source, + opacity: initialOpacity, + id: "rasterLayer" ) - self.rasterLayerState = RasterLayerState(source: source) + } + + func selectLayer(_ layer: GsiLayer) { + guard let selected = availableLayers.first(where: { $0.id == layer.id }) else { + assertionFailure("Unknown GSI layer: \(layer.id)") + return + } + selectedLayer = selected + rasterLayerState.source = selected.source + } + + func setOpacity(_ value: Double) { + let resolved = min(max(value, 0.0), 1.0) + opacity = resolved + rasterLayerState.opacity = resolved } } diff --git a/samples/MapConductorSampleApp/Sources/pages/startup/StartUpPage.swift b/samples/MapConductorSampleApp/Sources/pages/startup/StartUpPage.swift index 562d370..bec46f8 100644 --- a/samples/MapConductorSampleApp/Sources/pages/startup/StartUpPage.swift +++ b/samples/MapConductorSampleApp/Sources/pages/startup/StartUpPage.swift @@ -41,7 +41,7 @@ struct StartUpPage: View { Text("- Demo Application -") .font(.system(size: 22, weight: .semibold)) - Text("A unified mapping library that provides a common API for multiple map providers including Google Maps, Mapbox, HERE, and ArcGIS. Write once, deploy across all major mapping platforms.") + Text("A unified mapping library that provides a common API for multiple map providers including Google Maps, Mapbox, MapKit, and ArcGIS. Write once, deploy across all major mapping platforms.") .multilineTextAlignment(.leading) Text("Features") diff --git a/samples/MapConductorSampleApp/Sources/support/HeaderRecordingTileServer.swift b/samples/MapConductorSampleApp/Sources/support/HeaderRecordingTileServer.swift new file mode 100644 index 0000000..ceda826 --- /dev/null +++ b/samples/MapConductorSampleApp/Sources/support/HeaderRecordingTileServer.swift @@ -0,0 +1,225 @@ +import Foundation +import Network +import UIKit + +/// タイル要求に**実際に載っていたヘッダ**を記録する計測用サーバ。 +/// +/// `RasterLayerState` の `userAgent` / `extraHeaders` が本当に送信されているかは、 +/// 送り出す側のコードを読んでも分からない(プロバイダのネイティブ SDK が握っている)。 +/// 受け取る側を自分で立てて、届いたヘッダをそのまま読むのが唯一の確実な確認方法。 +/// +/// アプリ内 127.0.0.1 に立てるので、シミュレータでも実機でも外部依存なしに動く。 +/// docker やプロキシを用意すると実機から到達できず、実機固有の挙動を取り逃す。 +/// +/// 作りは `MapConductorCore.LocalTileServer` に合わせてあるが、こちらは計測専用なので +/// keep-alive も同時接続制限も持たない(1 リクエスト応答して閉じる)。 +final class HeaderRecordingTileServer { + /// 記録した 1 リクエスト分。ヘッダ名は小文字で正規化して入れる。 + struct Record { + let path: String + let headers: [String: String] + } + + private let listener: NWListener + private let queue: DispatchQueue + private let lock = NSLock() + + private var records: [Record] = [] + private(set) var baseUrl: String + + /// 応答する PNG。毎回生成すると重いので 1 回だけ作る。 + private let tilePng: Data + + private init(listener: NWListener, queue: DispatchQueue, baseUrl: String, tilePng: Data) { + self.listener = listener + self.queue = queue + self.baseUrl = baseUrl + self.tilePng = tilePng + } + + // MARK: - 読み出し + + var requestCount: Int { + lock.lock() + defer { lock.unlock() } + return records.count + } + + /// 直近のリクエストに載っていたヘッダ値。`name` は大文字小文字を問わない。 + func lastHeader(_ name: String) -> String? { + lock.lock() + defer { lock.unlock() } + return records.last?.headers[name.lowercased()] + } + + /// これまでに一度でも観測したヘッダ値。 + /// + /// 直近だけを見ると取りこぼす: ベースマップのスタイル要求とラスタタイル要求が + /// 混ざって飛ぶので、「最後の 1 本」がこちらの狙ったタイルとは限らない。 + func anyHeader(_ name: String) -> String? { + let key = name.lowercased() + lock.lock() + defer { lock.unlock() } + for record in records.reversed() { + if let value = record.headers[key] { return value } + } + return nil + } + + func reset() { + lock.lock() + records.removeAll() + lock.unlock() + } + + func urlTemplate() -> String { + "\(baseUrl)/tiles/{z}/{x}/{y}.png" + } + + func stop() { + listener.cancel() + } + + // MARK: - 起動 + + static func start() -> HeaderRecordingTileServer { + let queue = DispatchQueue(label: "MapConductorSampleApp.HeaderRecordingTileServer", attributes: .concurrent) + + let listener: NWListener + do { + listener = try NWListener(using: .tcp) + } catch { + fatalError("計測サーバの listener を作れませんでした: \(error)") + } + + let server = HeaderRecordingTileServer( + listener: listener, + queue: queue, + baseUrl: "http://127.0.0.1:0", + tilePng: makeTilePng() + ) + listener.newConnectionHandler = { [weak server] connection in + server?.handleConnection(connection) + } + + let ready = DispatchSemaphore(value: 0) + var startError: NWError? + listener.stateUpdateHandler = { state in + switch state { + case .ready, .failed: + if case let .failed(error) = state { startError = error } + ready.signal() + default: + break + } + } + + listener.start(queue: queue) + ready.wait() + + if let startError { + fatalError("計測サーバを起動できませんでした: \(startError)") + } + guard let port = listener.port else { + fatalError("計測サーバがポートを取得できませんでした。") + } + + server.baseUrl = "http://127.0.0.1:\(port.rawValue)" + return server + } + + /// 目視でも「敷かれている」と分かるよう、半透明のマゼンタで塗った 256px タイルを返す。 + private static func makeTilePng() -> Data { + let size = CGSize(width: 256, height: 256) + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { context in + UIColor.magenta.withAlphaComponent(0.35).setFill() + context.fill(CGRect(origin: .zero, size: size)) + } + return image.pngData() ?? Data() + } + + // MARK: - 受信 + + private func handleConnection(_ connection: NWConnection) { + connection.start(queue: queue) + receive(connection: connection, buffer: Data()) + } + + private func receive(connection: NWConnection, buffer: Data) { + connection.receive(minimumIncompleteLength: 1, maximumLength: 64 * 1024) { [weak self] data, _, isComplete, error in + guard let self else { return } + + var next = buffer + if let data { next.append(data) } + + if next.count > 64 * 1024 { + connection.cancel() + return + } + + if let headerRange = next.range(of: Data([13, 10, 13, 10])) { + let headerData = next.subdata(in: 0.. Void let isExpanded: Bool @@ -37,14 +37,15 @@ struct Sidebar: View { ScrollView { VStack(alignment: .leading, spacing: 8) { - ForEach(items) { item in - SidebarItemView( - item: item, - isSelected: item.id == selectedItemId - ) { - onItemClick(item) - onToggleSidebar() - } + ForEach(sections) { section in + SidebarSectionView( + section: section, + selectedItemId: selectedItemId, + onItemClick: { item in + onItemClick(item) + onToggleSidebar() + } + ) } } .padding(.vertical, 12) @@ -60,6 +61,32 @@ struct Sidebar: View { } } +private struct SidebarSectionView: View { + let section: SidebarSection + let selectedItemId: String + let onItemClick: (SidebarItem) -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + Text(section.title) + .font(.system(size: 13, weight: .bold)) + .foregroundColor(.accentColor) + .padding(.leading, 20) + .padding(.top, 12) + .padding(.bottom, 4) + + ForEach(section.items) { item in + SidebarItemView( + item: item, + isSelected: item.id == selectedItemId + ) { + onItemClick(item) + } + } + } + } +} + private struct SidebarItemView: View { let item: SidebarItem let isSelected: Bool diff --git a/samples/MapConductorSampleApp/Sources/ui/sidebar/SidebarItem.swift b/samples/MapConductorSampleApp/Sources/ui/sidebar/SidebarItem.swift index 28f5f39..098c184 100644 --- a/samples/MapConductorSampleApp/Sources/ui/sidebar/SidebarItem.swift +++ b/samples/MapConductorSampleApp/Sources/ui/sidebar/SidebarItem.swift @@ -4,3 +4,9 @@ struct SidebarItem: Identifiable { let id: String let title: String } + +struct SidebarSection: Identifiable { + let id: String + let title: String + let items: [SidebarItem] +} diff --git a/samples/MapConductorSampleApp/UITests/LongdoInfoBubbleUITests.swift b/samples/MapConductorSampleApp/UITests/LongdoInfoBubbleUITests.swift new file mode 100644 index 0000000..f480d31 --- /dev/null +++ b/samples/MapConductorSampleApp/UITests/LongdoInfoBubbleUITests.swift @@ -0,0 +1,94 @@ +import XCTest + +/// Real-touch verification of Longdo marker taps and InfoBubbles. +/// +/// The multiple-info-bubbles page opens all three bubbles on load and toggles a +/// bubble when its marker is tapped; a map (background) tap closes every bubble. +/// Marker 1 sits exactly at the camera center, so its icon hangs just above the +/// view center (bottom-anchored) — a tap slightly above center hits the marker. +final class LongdoInfoBubbleUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + private func waitUntilGone(_ element: XCUIElement, timeout: TimeInterval) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while element.exists && Date() < deadline { + RunLoop.current.run(until: Date().addingTimeInterval(0.25)) + } + return !element.exists + } + + func testMarkerTapTogglesInfoBubble() throws { + let app = XCUIApplication() + app.launchEnvironment["MAPCONDUCTOR_SAMPLE_INIT_PAGE"] = "multiple-info-bubbles" + app.launchEnvironment["MAPCONDUCTOR_SAMPLE_PROVIDER"] = "longdo" + app.launch() + + let bubble1 = app.staticTexts["Restaurant A"] + let bubble2 = app.staticTexts["Hotel B"] + XCTAssertTrue(bubble1.waitForExistence(timeout: 60), "initial bubbles never appeared") + XCTAssertTrue(bubble2.exists, "bubble 2 missing on load") + sleep(3) + + let window = app.windows.firstMatch + let markerTap = window + .coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) + .withOffset(CGVector(dx: 0, dy: -20)) + + // Tap the marker: only bubble 1 toggles off. A leaked map-click would + // clear every bubble instead. + markerTap.tap() + XCTAssertTrue(waitUntilGone(bubble1, timeout: 10), "marker tap did not toggle bubble 1 off") + XCTAssertTrue(bubble2.exists, "marker tap must not clear the other bubbles") + + // Tap again: the bubble re-opens (the actual user scenario). + markerTap.tap() + XCTAssertTrue(bubble1.waitForExistence(timeout: 10), "marker tap did not re-open bubble 1") + XCTAssertTrue(bubble2.exists) + + // Background tap far from any marker closes all bubbles. + window.coordinate(withNormalizedOffset: CGVector(dx: 0.15, dy: 0.85)).tap() + XCTAssertTrue(waitUntilGone(bubble1, timeout: 10), "map tap did not close bubble 1") + XCTAssertTrue(waitUntilGone(bubble2, timeout: 5), "map tap did not close bubble 2") + } + + /// Long-press pickup drag (custom implementation; the SDK's marker dragging is disabled). + /// Picks up a store marker on the StoreMap page, drops it in the empty ocean, then taps the + /// drop point: the info bubble opening there proves the marker really moved. + func testMarkerLongPressDrag() throws { + let app = XCUIApplication() + app.launchEnvironment["MAPCONDUCTOR_SAMPLE_INIT_PAGE"] = "map-basic" + app.launchEnvironment["MAPCONDUCTOR_SAMPLE_PROVIDER"] = "longdo" + app.launch() + sleep(18) + + let window = app.windows.firstMatch + let bubbleSignal = app.buttons["Get Directions"] + let dropOffset = CGVector(dx: 0.25, dy: 0.28) + let dropPoint = window.coordinate(withNormalizedOffset: dropOffset) + + // The isolated north-shore pin sits near (0.41, 0.33); walk a small grid to absorb + // per-run layout differences. + var dragged = false + outer: for dy in [0.0, -0.015, 0.015] { + for dx in [0.0, -0.02, 0.02] { + let candidate = window.coordinate( + withNormalizedOffset: CGVector(dx: 0.41 + dx, dy: 0.325 + dy) + ) + candidate.press(forDuration: 0.9, thenDragTo: dropPoint) + sleep(2) + dropPoint.tap() + if bubbleSignal.waitForExistence(timeout: 4) { + dragged = true + break outer + } + // Close anything that opened and reset for the next probe. + window.coordinate(withNormalizedOffset: CGVector(dx: 0.8, dy: 0.85)).tap() + sleep(1) + } + } + XCTAssertTrue(dragged, "long-press drag never moved a marker to the drop point") + } +} diff --git a/samples/MapConductorSampleApp/UITests/SpiderfyUITests.swift b/samples/MapConductorSampleApp/UITests/SpiderfyUITests.swift new file mode 100644 index 0000000..65caa3d --- /dev/null +++ b/samples/MapConductorSampleApp/UITests/SpiderfyUITests.swift @@ -0,0 +1,126 @@ +import XCTest + +/// On-device verification of the marker-clustering updates ported from the +/// React SDK: prepareExpand, recomputed cluster centers, and spiderfy. +/// +/// The app is launched directly on the Post Office Cluster page (MapLibre, +/// Tokyo Station, zoom 13 == spiderfyMinZoom). Cluster screen positions are +/// data-dependent, so the test walks a grid of candidate points near screen +/// center until one tap opens a fan, using the demo's spiderfyStatus overlay +/// (wired to onSpiderfyChange) as the ground truth. +final class SpiderfyUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + private func attach(_ app: XCUIApplication, _ name: String) { + let shot = XCTAttachment(screenshot: app.screenshot()) + shot.name = name + shot.lifetime = .keepAlways + add(shot) + } + + /// 条件が真になるまで待つ。時間ではなく結果を待つための道具。 + private func waitUntil(timeout: TimeInterval, _ condition: () -> Bool) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if condition() { return true } + RunLoop.current.run(until: Date().addingTimeInterval(0.25)) + } + return condition() + } + + private func waitForLabel( + _ element: XCUIElement, + _ expected: String, + timeout: TimeInterval + ) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if element.exists && element.label == expected { return true } + RunLoop.current.run(until: Date().addingTimeInterval(0.25)) + } + return element.exists && element.label == expected + } + + func testSpiderfyOpenCollapseAndPrepareExpand() throws { + let app = XCUIApplication() + app.launchEnvironment["MAPCONDUCTOR_SAMPLE_INIT_PAGE"] = "marker-postoffice-cluster" + app.launchEnvironment["MAPCONDUCTOR_SAMPLE_PROVIDER"] = "maplibre" + app.launch() + + let status = app.staticTexts["spiderfyStatus"] + XCTAssertTrue(status.waitForExistence(timeout: 30), "status overlay never appeared") + + // prepareExpand must have fired for the initial marker render. + // + // 固定 sleep で待たない。マップ読み込み・郵便局データの取り込み・最初のクラスタ描画に + // かかる時間は端末の状態でかなり変わる(他のテストのあとに走ると目に見えて遅い)。 + // 以前はここが `sleep(15)` + `waitForExistence(5)` だったため、スイート全体を + // 回したときだけ落ちていた。状態が変わるまで粘る形にして、時間ではなく結果を待つ。 + let prep = app.staticTexts["prepareExpandStatus"] + XCTAssertTrue(prep.waitForExistence(timeout: 60), "prepareExpand の読み出しが出ない") + let firedByDeadline = waitUntil(timeout: 120) { prep.label != "prepare:0" } + attach(app, "01_loaded") + XCTAssertTrue(firedByDeadline, "prepareExpand never fired on initial render") + + let window = app.windows.firstMatch + // Cluster layout shifts slightly between runs (camera zoom settles at + // marginally different values), so fixed positions are unreliable. + // Walk a dense grid instead — spacing ~78pt is below the cluster + // marker hit radius (icon + tap tolerance), so any cluster inside the + // scanned region gets hit. Stop at the first tap that opens a fan. + var candidates: [(Double, Double)] = [] + for row in 0..<9 { + for col in 0..<10 { + let x = 0.08 + Double(col) * (0.84 / 9.0) + let y = 0.10 + Double(row) * (0.68 / 8.0) + // Skip the provider dropdown (top-right) and Controls (bottom). + if y < 0.14 && x > 0.68 { continue } + candidates.append((x, y)) + } + } + + var openedAt: XCUICoordinate? + for (index, frac) in candidates.enumerated() { + let point = window.coordinate( + withNormalizedOffset: CGVector(dx: frac.0, dy: frac.1)) + point.tap() + if waitForLabel(status, "SPIDERFY_ON", timeout: 1.5) { + attach(app, "02_spiderfy_open_tap\(index)") + openedAt = point + break + } + } + guard let clusterPoint = openedAt else { + attach(app, "02_no_cluster_hit") + XCTFail("no candidate tap opened a spiderfy fan") + return + } + + // Re-tapping the kept cluster marker collapses the fan. + clusterPoint.tap() + XCTAssertTrue( + waitForLabel(status, "SPIDERFY_OFF", timeout: 5), + "re-tap did not collapse the fan") + attach(app, "03_retap_collapsed") + + // Open again, then pan the map: recluster must auto-collapse. + clusterPoint.tap() + XCTAssertTrue( + waitForLabel(status, "SPIDERFY_ON", timeout: 5), + "second open failed") + attach(app, "04_reopened") + + let dragFrom = window.coordinate( + withNormalizedOffset: CGVector(dx: 0.50, dy: 0.78)) + let dragTo = window.coordinate( + withNormalizedOffset: CGVector(dx: 0.64, dy: 0.88)) + dragFrom.press(forDuration: 0.1, thenDragTo: dragTo) + XCTAssertTrue( + waitForLabel(status, "SPIDERFY_OFF", timeout: 12), + "pan-triggered recluster did not collapse the fan") + attach(app, "05_pan_collapsed") + } +} diff --git a/samples/MapConductorSampleApp/project.yml b/samples/MapConductorSampleApp/project.yml index 8af6c51..01176ae 100644 --- a/samples/MapConductorSampleApp/project.yml +++ b/samples/MapConductorSampleApp/project.yml @@ -11,19 +11,29 @@ packages: path: ../../ios-for-mapkit ios-for-maplibre: path: ../../ios-for-maplibre + ios-for-arcgis: + path: ../../ios-for-arcgis ios-for-googlemaps: path: ../../ios-for-googlemaps + ios-for-here: + path: ../../ios-for-here ios-heatmap: - url: https://github.com/MapConductor/ios-heatmap - revision: f87531a5311ad6a8f1f6d08ce9ae9d2086adea67 + path: ../../ios-heatmap + ios-geojson-layer: + path: ../../ios-geojson-layer ios-marker-cluster: - url: https://github.com/MapConductor/ios-marker-cluster - revision: b923daccd4876ae571d628da7a1fc04ea0508443 + path: ../../ios-marker-cluster ios-maps-sdk: url: https://github.com/googlemaps/ios-maps-sdk version: 10.7.0 ios-for-mapbox: path: ../../ios-for-mapbox + ios-for-tomtom: + path: ../../ios-for-tomtom + ios-for-maptiler: + path: ../../ios-for-maptiler + ios-for-longdo: + path: ../../ios-for-longdo ZIPFoundation: url: https://github.com/weichsel/ZIPFoundation version: 0.9.19 @@ -33,7 +43,10 @@ targets: type: application platform: iOS sources: - - Sources + - path: Sources + excludes: + - "pages/polygon/holes" + - "ContentView 2.swift" resources: - Resources/Assets.xcassets postBuildScripts: @@ -56,7 +69,7 @@ targets: INFOPLIST_FILE: Info.plist PRODUCT_BUNDLE_IDENTIFIER: com.mapconductor.sample CODE_SIGN_STYLE: Automatic - DEVELOPMENT_TEAM: "" + DEVELOPMENT_TEAM: "4DUVX7999A" CODE_SIGN_IDENTITY: "Apple Development" ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon configs: @@ -74,12 +87,24 @@ targets: product: MapConductorForGoogleMaps - package: ios-for-maplibre product: MapConductorForMapLibre + - package: ios-for-arcgis + product: MapConductorForArcGIS - package: ios-for-mapkit product: MapConductorForMapKit - package: ios-for-mapbox product: MapConductorForMapbox + - package: ios-for-tomtom + product: MapConductorForTomTom + - package: ios-for-maptiler + product: MapConductorForMapTiler + - package: ios-for-longdo + product: MapConductorForLongdo - package: ios-heatmap product: MapConductorHeatmap + - package: ios-geojson-layer + product: MapConductorGeoJSON + - package: ios-for-here + product: MapConductorForHERE - package: ios-marker-cluster product: MapConductorMarkerCluster - package: ios-maps-sdk @@ -87,6 +112,20 @@ targets: - package: ZIPFoundation product: ZIPFoundation + MapConductorSampleAppUITests: + type: bundle.ui-testing + platform: iOS + sources: + - UITests + dependencies: + - target: MapConductorSampleApp + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.mapconductor.sample.uitests + CODE_SIGN_STYLE: Automatic + DEVELOPMENT_TEAM: "4DUVX7999A" + GENERATE_INFOPLIST_FILE: YES + schemes: MapConductorSampleApp: build: @@ -96,6 +135,8 @@ schemes: config: Debug test: config: Debug + targets: + - MapConductorSampleAppUITests profile: config: Release analyze: diff --git a/scripts/build-xcframeworks.sh b/scripts/build-xcframeworks.sh new file mode 100755 index 0000000..9832754 --- /dev/null +++ b/scripts/build-xcframeworks.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +set -euo pipefail + +# core/geojson/heatmap/marker-cluster ship as CocoaPods source pods instead of prebuilt +# xcframeworks (see MapConductorCore.podspec and ios-sdk/CLAUDE.md's "iOS Provider Distribution" +# section): none of them wrap a third-party vendor SDK, so there's no redistribution concern, and +# compiling them fresh in the consuming app avoids the module-verification failures that show up +# when a prebuilt xcframework imports a source-compiled sibling module (deployment-target +# normalization mismatches, `-import-underlying-module` self-verification failures - also note +# `xcodebuild -create-xcframework` unconditionally requires BUILD_LIBRARY_FOR_DISTRIBUTION=YES's +# swiftinterface output for any Swift framework, so a prebuilt xcframework can never drop it). +# They're kept in all_modules below for manual/debugging use only - not part of default_modules. +# maplibre (and any future mapkit/mapbox/arcgis/here provider) stays genuinely prebuilt because it +# wraps a real vendor SDK shipped as a *dynamic* framework - see the CLAUDE.md section for the +# static-vs-dynamic decision rule that governs this per provider. + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BUILD_DIR="${BUILD_DIR:-$ROOT_DIR/build/xcframeworks}" +DEVELOPER_DIR="${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer}" +export DEVELOPER_DIR +export MAPCONDUCTOR_BUILD_XCFRAMEWORK=1 + +default_modules=(maplibre) +all_modules=(core geojson heatmap marker-cluster mapkit maplibre mapbox arcgis here) + +module_config() { + case "$1" in + core) package_dir="ios-sdk-core"; scheme="mapconductor-core"; product="MapConductorCore" ;; + geojson) package_dir="ios-geojson-layer"; scheme="mapconductor-geojson-layer"; product="MapConductorGeoJSON" ;; + heatmap) package_dir="ios-heatmap"; scheme="mapconductor-heatmap"; product="MapConductorHeatmap" ;; + marker-cluster) package_dir="ios-marker-cluster"; scheme="mapconductor-marker-cluster"; product="MapConductorMarkerCluster" ;; + mapkit) package_dir="ios-for-mapkit"; scheme="mapconductor-for-mapkit"; product="MapConductorForMapKit" ;; + maplibre) package_dir="ios-for-maplibre"; scheme="mapconductor-for-maplibre"; product="MapConductorForMapLibre" ;; + mapbox) package_dir="ios-for-mapbox"; scheme="mapconductor-for-mapbox"; product="MapConductorForMapbox" ;; + arcgis) package_dir="ios-for-arcgis"; scheme="mapconductor-for-arcgis"; product="MapConductorForArcGIS" ;; + here) package_dir="ios-for-here"; scheme="mapconductor-for-here"; product="MapConductorForHERE" ;; + *) echo "Unknown module: $1" >&2; exit 2 ;; + esac +} + +if [[ "${1:-}" == "--all" ]]; then + modules=("${all_modules[@]}") +elif [[ "$#" -gt 0 ]]; then + modules=("$@") +else + modules=("${default_modules[@]}") +fi + +mkdir -p "$BUILD_DIR/archives" "$BUILD_DIR/output" + +archive_module() { + local module="$1" + local package_dir scheme product + module_config "$module" + + local source_dir="$ROOT_DIR/$package_dir" + local device_archive="$BUILD_DIR/archives/$product-iOS.xcarchive" + local simulator_archive="$BUILD_DIR/archives/$product-Simulator.xcarchive" + local device_derived="$BUILD_DIR/derived/$product-iOS" + local simulator_derived="$BUILD_DIR/derived/$product-Simulator" + local output="$BUILD_DIR/output/$product.xcframework" + + rm -rf "$device_archive" "$simulator_archive" "$device_derived" "$simulator_derived" "$output" + + echo "Archiving $product for iOS" + ( + cd "$source_dir" + xcodebuild archive \ + -scheme "$scheme" \ + -destination "generic/platform=iOS" \ + -archivePath "$device_archive" \ + -derivedDataPath "$device_derived" \ + SKIP_INSTALL=NO \ + BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ + CODE_SIGNING_ALLOWED=NO \ + -quiet + ) + + echo "Archiving $product for iOS Simulator" + ( + cd "$source_dir" + xcodebuild archive \ + -scheme "$scheme" \ + -destination "generic/platform=iOS Simulator" \ + -archivePath "$simulator_archive" \ + -derivedDataPath "$simulator_derived" \ + SKIP_INSTALL=NO \ + BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ + CODE_SIGNING_ALLOWED=NO \ + -quiet + ) + + local device_framework="$device_archive/Products/usr/local/lib/$product.framework" + local simulator_framework="$simulator_archive/Products/usr/local/lib/$product.framework" + if [[ ! -d "$device_framework" || ! -d "$simulator_framework" ]]; then + echo "Framework archive missing for $product" >&2 + exit 1 + fi + + install_swift_modules "$device_derived" "$device_framework" "$product" "iphoneos" "ios" + install_swift_modules "$simulator_derived" "$simulator_framework" "$product" "iphonesimulator" "ios-simulator" + + local create_args=( + -create-xcframework + -framework "$device_framework" + -framework "$simulator_framework" + ) + local device_dsym="$device_archive/dSYMs/$product.framework.dSYM" + [[ -d "$device_dsym" ]] && create_args+=( -debug-symbols "$device_dsym" ) + create_args+=( -output "$output" ) + + xcodebuild "${create_args[@]}" +} + +install_swift_modules() { + local derived_data="$1" + local framework="$2" + local product="$3" + local sdk="$4" + local triple_suffix="$5" + local modules_dir="$framework/Modules/$product.swiftmodule" + local found=false + + mkdir -p "$modules_dir" + for architecture in arm64 x86_64; do + local objects_dir + objects_dir="$(find "$derived_data/Build/Intermediates.noindex/ArchiveIntermediates" \ + -path "*/Release-$sdk/$product.build/Objects-normal/$architecture" \ + -type d -print -quit)" + [[ -n "$objects_dir" ]] || continue + found=true + + for extension in swiftinterface private.swiftinterface package.swiftinterface swiftdoc swiftmodule abi.json; do + local source="$objects_dir/$product.$extension" + [[ -f "$source" ]] || continue + cp "$source" "$modules_dir/$architecture-apple-$triple_suffix.$extension" + done + done + + if [[ "$found" != true ]]; then + echo "Swift module interfaces missing for $product ($sdk)" >&2 + exit 1 + fi +} + +for module in "${modules[@]}"; do + archive_module "$module" +done + +echo "XCFrameworks written to $BUILD_DIR/output" diff --git a/scripts/commit-all.sh b/scripts/commit-all.sh new file mode 100755 index 0000000..1f1b474 --- /dev/null +++ b/scripts/commit-all.sh @@ -0,0 +1,136 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR=$(dirname "${SCRIPT_DIR}") + +FALSE=1 +TRUE=0 + +BLACK=$'\e[30m' +RED=$'\e[31m' +GREEN=$'\e[32m' +YELLOW=$'\e[33m' +BLUE=$'\e[34m' +MAGENTA=$'\e[35m' +CYAN=$'\e[36m' +WHITE=$'\e[37m' +RESET=$'\e[0m' +ASK_COLOR=$GREEN +SEPARATOR_COLOR=$BLUE + +git-codex-commit() { + if git diff --cached --quiet; then + echo "No changes staged. Please run 'git add' first." + return $FALSE + fi + + echo "Generating English commit message using Codex..." + + local prompt="Analyze the following git diff and generate a clear, concise git commit message in English following Conventional Commits format (e.g., feat: add login feature). Output ONLY the final commit message text. Absolutely NO explanations, NO greetings, and NO markdown code blocks." + local msg + msg=$(codex exec "$prompt + +$(git diff --cached)") + + if [ -n "$msg" ]; then + echo -e "\ncommit message:\n$msg\n" + git commit -m "$msg" + git push + else + echo "Error: Failed to generate commit message." + return $FALSE + fi +} + +no-any-change() { + local target_dir=$1 + local save_dir + save_dir=$(pwd) + + cd "$target_dir" + + if [[ -z "$(git status --porcelain)" ]]; then + cd "$save_dir" + else + cd "$save_dir" + return $FALSE + fi +} + +has-staged-change() { + ! git diff --cached --quiet +} + +has-unstaged-change() { + ! git diff --quiet || [[ -n "$(git ls-files --others --exclude-standard)" ]] +} + + +ask-to-commit() { + TARGET_DIR=$1 + SAVE_DIR=$(pwd) + if [[ -z "${TARGET_DIR}" ]]; then + return $FALSE + fi + if [ -f "${TARGET_DIR}" ]; then + TARGET_DIR=$(dirname "$TARGET_DIR") + fi + BASE_NAME=$(basename "$TARGET_DIR") + + if no-any-change "$TARGET_DIR"; then + echo "Nothing to commit at ${BASE_NAME}" + return $FALSE + fi + + cd "$TARGET_DIR" + echo + echo -e "${SEPARATOR_COLOR}---[ ${TARGET_DIR} ]--------------${RESET}" + git status + echo + + if has-unstaged-change; then + read -n1 -p "${ASK_COLOR}Git add all unstaged changes at ${BASE_NAME}? (y/N):${RESET}" yn + echo + if [[ $yn = [yY] ]]; then + git add -A + git status + echo + git-codex-commit + cd "$SAVE_DIR" + return + fi + fi + + if ! has-staged-change; then + echo "No staged changes at ${BASE_NAME}" + cd "$SAVE_DIR" + return $FALSE + fi + + read -n1 -p "${ASK_COLOR}Git commit at ${BASE_NAME} directory? (y/N):${RESET}" yn + echo + if [[ $yn = [yY] ]]; then + git-codex-commit + cd "$SAVE_DIR" + else + echo "skip committing" + echo $FALSE + cd "$SAVE_DIR" + fi +} + +ask-to-commit "${ROOT_DIR}/ios-sdk-core" +ask-to-commit "${ROOT_DIR}/ios-for-googlemaps" +ask-to-commit "${ROOT_DIR}/ios-for-arcgis" +ask-to-commit "${ROOT_DIR}/ios-for-here" +ask-to-commit "${ROOT_DIR}/ios-for-maplibre" +ask-to-commit "${ROOT_DIR}/ios-for-mapbox" +ask-to-commit "${ROOT_DIR}/ios-for-maptiler" +ask-to-commit "${ROOT_DIR}/ios-for-longdo" +ask-to-commit "${ROOT_DIR}/ios-for-mapkit" +ask-to-commit "${ROOT_DIR}/ios-for-tomtom" +ask-to-commit "${ROOT_DIR}/ios-heatmap" +#ask-to-commit "${ROOT_DIR}/ios-icons" +ask-to-commit "${ROOT_DIR}/ios-geojson-layer" +ask-to-commit "${ROOT_DIR}/ios-marker-cluster" +ask-to-commit "${ROOT_DIR}/" diff --git a/scripts/sync-module-branches.sh b/scripts/sync-module-branches.sh new file mode 100755 index 0000000..8e61ac2 --- /dev/null +++ b/scripts/sync-module-branches.sh @@ -0,0 +1,207 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "${SCRIPT_DIR}")" + +TARGET_BRANCH="" +BASE_BRANCH="main" +FETCH=1 +PULL=0 +INCLUDE_ROOT=0 +DRY_RUN=0 + +usage() { + cat <<'USAGE' +Usage: + scripts/sync-module-branches.sh [branch] [options] + +Synchronize all git submodules to the same branch. + +By default, the target branch is the current branch of the parent repository. +If a submodule already has the target branch, the script switches to it. +If origin/ exists, the script creates/tracks the local branch from it. +Otherwise, the script creates the target branch from origin/main or local main. + +Options: + -b, --branch Target branch. Defaults to the parent repository branch. + --base Base branch for newly created branches. Defaults to main. + --no-fetch Do not fetch origin before checking remote branches. + --pull Fast-forward pull after switching to the target branch. + --include-root Also switch the parent repository. + --dry-run Print actions without changing branches. + -h, --help Show this help. + +Examples: + scripts/sync-module-branches.sh + scripts/sync-module-branches.sh feat/agp-9.2.1 --pull + scripts/sync-module-branches.sh --branch main --include-root +USAGE +} + +log() { + printf '%s\n' "$*" +} + +run() { + if [[ "${DRY_RUN}" -eq 1 ]]; then + printf ' DRY-RUN:' + printf ' %q' "$@" + printf '\n' + else + "$@" + fi +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + -b|--branch) + TARGET_BRANCH="${2:-}" + if [[ -z "${TARGET_BRANCH}" ]]; then + log "Missing branch name for $1" + exit 1 + fi + shift 2 + ;; + --base) + BASE_BRANCH="${2:-}" + if [[ -z "${BASE_BRANCH}" ]]; then + log "Missing branch name for --base" + exit 1 + fi + shift 2 + ;; + --no-fetch) + FETCH=0 + shift + ;; + --pull) + PULL=1 + shift + ;; + --include-root) + INCLUDE_ROOT=1 + shift + ;; + --dry-run) + DRY_RUN=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + -*) + log "Unknown option: $1" + usage + exit 1 + ;; + *) + if [[ -n "${TARGET_BRANCH}" ]]; then + log "Unexpected argument: $1" + usage + exit 1 + fi + TARGET_BRANCH="$1" + shift + ;; + esac + done +} + +current_parent_branch() { + git -C "${ROOT_DIR}" symbolic-ref --quiet --short HEAD +} + +ensure_clean_worktree() { + local repo_dir="$1" + local label="$2" + + if [[ -n "$(git -C "${repo_dir}" status --porcelain)" ]]; then + log "Refusing to switch ${label}: worktree has uncommitted changes." + git -C "${repo_dir}" status --short + exit 1 + fi +} + +has_local_branch() { + local repo_dir="$1" + local branch="$2" + git -C "${repo_dir}" show-ref --verify --quiet "refs/heads/${branch}" +} + +has_remote_branch() { + local repo_dir="$1" + local branch="$2" + git -C "${repo_dir}" show-ref --verify --quiet "refs/remotes/origin/${branch}" +} + +sync_repo_branch() { + local repo_dir="$1" + local label="$2" + + log "==> ${label}" + ensure_clean_worktree "${repo_dir}" "${label}" + + if [[ "${FETCH}" -eq 1 ]]; then + run git -C "${repo_dir}" fetch --prune origin + fi + + if has_local_branch "${repo_dir}" "${TARGET_BRANCH}"; then + run git -C "${repo_dir}" switch "${TARGET_BRANCH}" + elif has_remote_branch "${repo_dir}" "${TARGET_BRANCH}"; then + run git -C "${repo_dir}" switch --track -c "${TARGET_BRANCH}" "origin/${TARGET_BRANCH}" + elif has_remote_branch "${repo_dir}" "${BASE_BRANCH}"; then + run git -C "${repo_dir}" switch -c "${TARGET_BRANCH}" "origin/${BASE_BRANCH}" + elif has_local_branch "${repo_dir}" "${BASE_BRANCH}"; then + run git -C "${repo_dir}" switch -c "${TARGET_BRANCH}" "${BASE_BRANCH}" + else + log "Cannot create ${TARGET_BRANCH} in ${label}: base branch '${BASE_BRANCH}' was not found." + exit 1 + fi + + if [[ "${PULL}" -eq 1 ]]; then + run git -C "${repo_dir}" pull --ff-only + fi + + if [[ "${DRY_RUN}" -eq 1 ]]; then + log " target branch: ${TARGET_BRANCH}" + else + log " current branch: $(git -C "${repo_dir}" branch --show-current)" + fi +} + +submodule_paths() { + git -C "${ROOT_DIR}" config --file .gitmodules --get-regexp '^submodule\..*\.path$' | + awk '{print $2}' +} + +main() { + parse_args "$@" + + if [[ -z "${TARGET_BRANCH}" ]]; then + TARGET_BRANCH="$(current_parent_branch)" + fi + + if [[ -z "${TARGET_BRANCH}" ]]; then + log "Could not determine target branch. Pass one with --branch." + exit 1 + fi + + log "Target branch: ${TARGET_BRANCH}" + log "Base branch: ${BASE_BRANCH}" + + if [[ "${INCLUDE_ROOT}" -eq 1 ]]; then + sync_repo_branch "${ROOT_DIR}" "$(basename "${ROOT_DIR}")" + fi + + while IFS= read -r path; do + [[ -z "${path}" ]] && continue + sync_repo_branch "${ROOT_DIR}/${path}" "${path}" + done < <(submodule_paths) +} + +main "$@" +