diff --git a/examples/basic/src/ClientMapRoutes.tsx b/examples/basic/src/ClientMapRoutes.tsx index 0027555..9ecb4a1 100644 --- a/examples/basic/src/ClientMapRoutes.tsx +++ b/examples/basic/src/ClientMapRoutes.tsx @@ -34,6 +34,7 @@ const CameraRestrictionPage = lazy(() => import('./pages/map/camerarestriction/C const TiltPage = lazy(() => import('./pages/map/tilt/TiltPage').then(m => ({ default: m.TiltPage }))); const UISettingsPage = lazy(() => import('./pages/map/uisettings/UISettingsPage').then(m => ({ default: m.UISettingsPage }))); const VisibleRegionPage = lazy(() => import('./pages/map/visibleregion/VisibleRegionPage').then(m => ({ default: m.VisibleRegionPage }))); +const ProjectedCoordinatesPage = lazy(() => import('./pages/map/projected/ProjectedCoordinatesPage').then(m => ({ default: m.ProjectedCoordinatesPage }))); const CameraSyncPage = lazy(() => import('./pages/map/camerasync/CameraSyncPage').then(m => ({ default: m.CameraSyncPage }))); const MarkerLabelPositionPage = lazy(() => import('./pages/marker/labelposition/MarkerLabelPositionPage').then(m => ({ default: m.MarkerLabelPositionPage }))); const MarkerAnimationPage = lazy(() => import('./pages/marker/animation/MarkerAnimationPage').then(m => ({ default: m.MarkerAnimationPage }))); @@ -91,6 +92,7 @@ function pageContent(page: string | undefined) { case 'tilt': return ; case 'ui-settings': return ; case 'visible-region': return ; + case 'projected-coordinates': return ; case 'camera-sync': return ; case 'marker': return ; case 'marker-label-position': return ; diff --git a/examples/basic/src/pages/map/projected/ProjectedCoordinatesPage.tsx b/examples/basic/src/pages/map/projected/ProjectedCoordinatesPage.tsx new file mode 100644 index 0000000..2a09123 --- /dev/null +++ b/examples/basic/src/pages/map/projected/ProjectedCoordinatesPage.tsx @@ -0,0 +1,183 @@ +import { useEffect, useState } from 'react'; +import { + ColorDefaultIcon, + MarkerAnimation, + Spherical, + createMarkerState, + type GeoPoint, + type MapCameraPosition, + type MapCoordinateSystem, + type MapDesignTypeInterface, + type MapViewHolder, + type MapViewStateInterface, + type MarkerState, +} from '@mapconductor/js-sdk-core'; +import { Marker } from '@mapconductor/js-sdk-react'; +import { ControlPanel } from '../../../components/ControlPanel'; +import { MapViewContainer } from '../../../MapViewContainer'; +import { useSampleI18n } from '../../../samples/i18n'; + +// android / ios の同ページと同じカメラ。並べて数値を突き合わせるため揃えてある。 +const INIT_CAMERA = { lat: 35.681236, lng: 139.767125, zoom: 12 }; + +type Holder = MapViewHolder; + +let dropCount = 0; + +/** + * タップした地点(赤)と、その投影座標を緯度経度へ戻した地点(青・小)に落とすマーカー。 + * + * 投影と逆投影が合っていれば、青は赤のピン先に重なる。タップのたびに新しい id に + * するので、Drop がそのつど再生される。青は zIndex 1 にして、赤より手前に描く。 + * 青は `iconSize` で小さくしている(android / ios の同ページと同じ)。 + */ +function dropMarkers(tapped: GeoPoint, holder: Holder | null): MarkerState[] { + dropCount += 1; + const markers = [ + createMarkerState({ + id: `projected-tapped-${dropCount}`, + position: tapped, + icon: new ColorDefaultIcon({ fillColor: '#e53935', label: 'T' }), + animation: MarkerAnimation.Drop, + }), + ]; + const projected = holder?.toProjectedPoint(tapped) ?? null; + const back = projected && holder ? holder.fromProjectedPoint(projected) : null; + if (back) { + markers.push(createMarkerState({ + id: `projected-back-${dropCount}`, + position: back, + icon: new ColorDefaultIcon({ fillColor: '#1e88e5', label: 'P', iconSize: 28 }), + animation: MarkerAnimation.Drop, + zIndex: 1, + })); + } + return markers; +} + +function formatSystem(system: MapCoordinateSystem): string { + return `${system.code ?? '(engine-defined)'} (${system.unit})`; +} + +/** + * 1 地点ぶん: 緯度経度、投影座標(地図エンジンの座標系での x / y。画面座標ではない)、 + * その x / y を緯度経度へ戻した値と元の地点とのずれ。ずれが 0 に近ければ往復できている。 + */ +function ProjectionLines({ label, point, holder, unavailable }: { + label: string; + point: GeoPoint | null; + holder: Holder | null; + unavailable: string; +}) { + const projected = point && holder ? holder.toProjectedPoint(point) : null; + const back = projected && holder ? holder.fromProjectedPoint(projected) : null; + const error = point && back ? Spherical.computeDistanceBetween(point, back) : null; + return ( + <> +

{label}: {point?.toUrlValue(6) ?? unavailable}

+

+   Projected: {projected ? `x=${projected.x.toFixed(2)}, y=${projected.y.toFixed(2)}` : unavailable} +

+

+   Back: {back?.toUrlValue(6) ?? unavailable}{error !== null ? ` (error ${error.toFixed(3)} m)` : ''} +

+ + ); +} + +function ProjectedCoordinatesContent({ holder, center, tapped }: { + holder: Holder | null; + center: GeoPoint | null; + tapped: GeoPoint | null; +}) { + const { t } = useSampleI18n(); + const unavailable = t({ + en: 'Unavailable', + ja: '取得できません', + 'es-419': 'No disponible', + de: 'Nicht verfügbar', + th: 'ไม่มีข้อมูล', + hi: 'उपलब्ध नहीं', + }); + + return ( + +

+ {t({ + en: 'Tap the map to project the tapped point.', + ja: '地図をタップすると、その地点の投影座標を表示します。', + 'es-419': 'Toca el mapa para proyectar el punto tocado.', + de: 'Tippen Sie auf die Karte, um den angetippten Punkt zu projizieren.', + th: 'แตะแผนที่เพื่อฉายพิกัดของจุดที่แตะ', + hi: 'टैप किए गए बिंदु को प्रक्षेपित करने के लिए मैप पर टैप करें।', + })} +

+

+ {t({ + en: 'Markers: red T = tapped, blue P = back from the projected point.', + ja: 'マーカー: 赤の T がタップ地点、青の P が投影座標から戻した地点。', + 'es-419': 'Marcadores: T rojo = punto tocado, P azul = de vuelta desde el punto proyectado.', + de: 'Marker: rotes T = angetippt, blaues P = aus dem projizierten Punkt zurückgerechnet.', + th: 'หมุด: T สีแดง = จุดที่แตะ, P สีน้ำเงิน = แปลงกลับจากพิกัดที่ฉาย', + hi: 'मार्कर: लाल T = टैप किया गया बिंदु, नीला P = प्रक्षेपित बिंदु से वापस बदला गया।', + })} +

+

+ {t({ en: 'Coordinate system', ja: '座標系', 'es-419': 'Sistema de coordenadas', de: 'Koordinatensystem', th: 'ระบบพิกัด', hi: 'निर्देशांक प्रणाली' })}: {holder ? formatSystem(holder.coordinateSystem) : unavailable} +

+ + +
+ ); +} + +export function ProjectedCoordinatesPage() { + const [state, setState] = useState> | null>(null); + const [center, setCenter] = useState(null); + const [tapped, setTapped] = useState(null); + // ホルダは地図が出来てから付く。描画のたびに読むと、付いた後に描き直しが + // 起きず取り残されるので、地図の通知で取り直して state に持つ。 + const [holder, setHolder] = useState(null); + const [markers, setMarkers] = useState([]); + // Some engines send no camera event when the map comes up: take the initial + // values from the state, retrying until the holder is attached. + useEffect(() => { + if (!state || holder) return; + const timer = window.setInterval(() => { + const attached = state.getMapViewHolder(); + if (!attached) return; + setHolder(attached); + setCenter(current => current ?? state.cameraPosition.position); + }, 300); + return () => window.clearInterval(timer); + }, [state, holder]); + const refresh = (camera: MapCameraPosition) => { + setHolder(state?.getMapViewHolder() ?? null); + setCenter(camera.position); + }; + + return ( + { + const current = state?.getMapViewHolder() ?? null; + setHolder(current); + setTapped(point); + setMarkers(dropMarkers(point, current)); + }} + > + {markers.map(marker => )} + + + ); +} diff --git a/examples/basic/src/samples/documentation/index.ts b/examples/basic/src/samples/documentation/index.ts index 880bf93..46625dd 100644 --- a/examples/basic/src/samples/documentation/index.ts +++ b/examples/basic/src/samples/documentation/index.ts @@ -14,6 +14,7 @@ import mapDesign from './pages/map-design'; import flyTo from './pages/fly-to'; import tilt from './pages/tilt'; import visibleRegion from './pages/visible-region'; +import projectedCoordinates from './pages/projected-coordinates'; import cameraSync from './pages/camera-sync'; import marker from './pages/marker'; import markerLabelPosition from './pages/marker-label-position'; @@ -56,6 +57,7 @@ const DOCUMENTATION: Record = { 'fly-to': flyTo, tilt, 'visible-region': visibleRegion, + 'projected-coordinates': projectedCoordinates, 'camera-sync': cameraSync, marker, 'marker-label-position': markerLabelPosition, diff --git a/examples/basic/src/samples/documentation/pages/projected-coordinates.ts b/examples/basic/src/samples/documentation/pages/projected-coordinates.ts new file mode 100644 index 0000000..8aa90b3 --- /dev/null +++ b/examples/basic/src/samples/documentation/pages/projected-coordinates.ts @@ -0,0 +1,55 @@ +import type { SamplePageDoc } from '../types'; + +const doc: SamplePageDoc = { + helpers: `function ProjectedValues({ holder, point }: { holder: MapViewHolder | null; point: GeoPoint | null }) { + if (!holder || !point) return

Tap the map to project the tapped point.

; + const projected = holder.toProjectedPoint(point); + const back = projected ? holder.fromProjectedPoint(projected) : null; + return
{JSON.stringify({
+    coordinateSystem: holder.coordinateSystem,
+    projected,
+    back: back?.toUrlValue(6),
+  }, null, 2)}
; +}`, + code: ` setTapped(point)} +/> + +`, + state: `const [tapped, setTapped] = useState(null);`, + explanation: { + en: [ + 'Convert between latitude/longitude and the coordinate system the map engine lays the map out in. These are not screen coordinates.', + 'coordinateSystem tells which system the values are in, such as EPSG:3857 in meters. Engines that only have Mercator, and globe display, use EPSG:3857.', + 'toProjectedPoint projects a point and fromProjectedPoint converts it back, so the round trip returns the original point.', + ], + ja: [ + '緯度経度と、地図エンジンが地図を並べている座標系の値を相互に変換します。画面座標ではありません。', + 'coordinateSystem は値がどの座標系か(EPSG:3857 のメートルなど)を示します。Mercator しか持たないエンジンと地球儀表示は EPSG:3857 です。', + 'toProjectedPoint で投影し、fromProjectedPoint で戻すと元の地点に戻ります。', + ], + 'es-419': [ + 'Convierte entre latitud/longitud y el sistema de coordenadas en que el motor dispone el mapa. No son coordenadas de pantalla.', + 'coordinateSystem indica en qué sistema están los valores, por ejemplo EPSG:3857 en metros. Los motores solo Mercator y la vista de globo usan EPSG:3857.', + 'toProjectedPoint proyecta un punto y fromProjectedPoint lo revierte, de modo que la ida y vuelta devuelve el punto original.', + ], + de: [ + 'Rechnet zwischen Breite/Länge und dem Koordinatensystem um, in dem die Karten-Engine die Karte anordnet. Das sind keine Bildschirmkoordinaten.', + 'coordinateSystem gibt an, in welchem System die Werte liegen, etwa EPSG:3857 in Metern. Reine Mercator-Engines und die Globusansicht verwenden EPSG:3857.', + 'toProjectedPoint projiziert einen Punkt, fromProjectedPoint rechnet ihn zurück; der Hin- und Rückweg ergibt wieder den ursprünglichen Punkt.', + ], + th: [ + 'แปลงระหว่างละติจูด/ลองจิจูด กับระบบพิกัดที่เอนจินแผนที่ใช้จัดวางแผนที่ ไม่ใช่พิกัดบนหน้าจอ', + 'coordinateSystem บอกว่าค่าอยู่ในระบบใด เช่น EPSG:3857 หน่วยเมตร เอนจินที่มีแต่ Mercator และการแสดงแบบลูกโลกใช้ EPSG:3857', + 'toProjectedPoint ฉายจุด และ fromProjectedPoint แปลงกลับ การแปลงไปกลับจึงได้จุดเดิม', + ], + hi: [ + 'अक्षांश/देशांतर और उस निर्देशांक प्रणाली के बीच रूपांतरण करें जिसमें मैप इंजन मैप को रखता है। ये स्क्रीन निर्देशांक नहीं हैं।', + 'coordinateSystem बताता है कि मान किस प्रणाली में हैं, जैसे मीटर में EPSG:3857। केवल Mercator वाले इंजन और ग्लोब दृश्य EPSG:3857 का उपयोग करते हैं।', + 'toProjectedPoint बिंदु को प्रक्षेपित करता है और fromProjectedPoint उसे वापस बदलता है, इसलिए आना-जाना मूल बिंदु लौटाता है।', + ], + }, +}; + +export default doc; diff --git a/examples/basic/src/samples/sampleIntro.ts b/examples/basic/src/samples/sampleIntro.ts index b1f49e0..af4004c 100644 --- a/examples/basic/src/samples/sampleIntro.ts +++ b/examples/basic/src/samples/sampleIntro.ts @@ -73,6 +73,17 @@ const INTROS: Record = Object.fromEntries([ hi: 'मैप को खिसकाने या ज़ूम करने पर दृश्य क्षेत्र अपडेट होता है।', }, ), + intro( + 'projected-coordinates', + { + en: 'Tap the map to see the tapped point in the map engine\'s coordinate system.', + ja: '地図をタップすると、その地点を地図エンジンの座標系で表示します。', + 'es-419': 'Toca el mapa para ver el punto en el sistema de coordenadas del motor de mapas.', + de: 'Tippen Sie auf die Karte, um den Punkt im Koordinatensystem der Karten-Engine zu sehen.', + th: 'แตะแผนที่เพื่อดูจุดที่แตะในระบบพิกัดของเอนจินแผนที่', + hi: 'मैप पर टैप करें, टैप किया गया बिंदु मैप इंजन की निर्देशांक प्रणाली में दिखेगा।', + }, + ), intro( 'camera-sync', { diff --git a/examples/basic/src/samples/sampleRegistry.ts b/examples/basic/src/samples/sampleRegistry.ts index f6ef67d..0911a1b 100644 --- a/examples/basic/src/samples/sampleRegistry.ts +++ b/examples/basic/src/samples/sampleRegistry.ts @@ -25,6 +25,7 @@ export const SAMPLE_PAGES: SamplePageDefinition[] = [ { id: 'tilt', label: { en: 'Tilt', ja: '傾き', 'es-419': 'Inclinación', de: 'Neigung', th: 'การเอียง', hi: 'झुकाव' }, group: 'Map', unavailableProviders: ['google-maps', 'mapkit'] }, { id: 'ui-settings', label: { en: 'UI Settings', ja: 'UI設定', 'es-419': 'Ajustes de interfaz', de: 'UI-Einstellungen', th: 'การตั้งค่า UI', hi: 'UI सेटिंग' }, group: 'Map' }, { id: 'visible-region', label: { en: 'Visible Region', ja: '表示領域', 'es-419': 'Región visible', de: 'Sichtbarer Bereich', th: 'พื้นที่ที่มองเห็น', hi: 'दृश्य क्षेत्र' }, group: 'Map' }, + { id: 'projected-coordinates', label: { en: 'Projected Coordinates', ja: '投影座標', 'es-419': 'Coordenadas proyectadas', de: 'Projizierte Koordinaten', th: 'พิกัดที่ฉายแล้ว', hi: 'प्रक्षेपित निर्देशांक' }, group: 'Map' }, { id: 'camera-sync', label: { en: 'Camera Sync', ja: 'カメラ同期', 'es-419': 'Sincronización de cámara', de: 'Kamerasynchronisation', th: 'การซิงก์กล้อง', hi: 'कैमरा सिंक' }, group: 'Map', showProviderSelector: false }, { id: 'marker', label: { en: 'Marker Icons', ja: 'マーカーアイコン', 'es-419': 'Iconos de marcadores', de: 'Marker-Icons', th: 'ไอคอนมาร์กเกอร์', hi: 'मार्कर आइकन' }, group: 'Marker' }, { id: 'marker-label-position', label: { en: 'Marker Label Position', ja: 'マーカーラベルの位置' }, group: 'Marker' },