Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/basic/src/ClientMapRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 })));
Expand Down Expand Up @@ -91,6 +92,7 @@ function pageContent(page: string | undefined) {
case 'tilt': return <TiltPage />;
case 'ui-settings': return <UISettingsPage />;
case 'visible-region': return <VisibleRegionPage />;
case 'projected-coordinates': return <ProjectedCoordinatesPage />;
case 'camera-sync': return <CameraSyncPage />;
case 'marker': return <MarkerIconsPage />;
case 'marker-label-position': return <MarkerLabelPositionPage />;
Expand Down
183 changes: 183 additions & 0 deletions examples/basic/src/pages/map/projected/ProjectedCoordinatesPage.tsx
Original file line number Diff line number Diff line change
@@ -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<unknown, unknown>;

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 (
<>
<p className="control-panel-note">{label}: {point?.toUrlValue(6) ?? unavailable}</p>
<p className="control-panel-note">
&nbsp;&nbsp;Projected: {projected ? `x=${projected.x.toFixed(2)}, y=${projected.y.toFixed(2)}` : unavailable}
</p>
<p className="control-panel-note">
&nbsp;&nbsp;Back: {back?.toUrlValue(6) ?? unavailable}{error !== null ? ` (error ${error.toFixed(3)} m)` : ''}
</p>
</>
);
}

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 (
<ControlPanel title={t({
en: 'Projected Coordinates',
ja: '投影座標',
'es-419': 'Coordenadas proyectadas',
de: 'Projizierte Koordinaten',
th: 'พิกัดที่ฉายแล้ว',
hi: 'प्रक्षेपित निर्देशांक',
})}>
<p className="control-panel-note">
{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: 'टैप किए गए बिंदु को प्रक्षेपित करने के लिए मैप पर टैप करें।',
})}
</p>
<p className="control-panel-note">
{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 = प्रक्षेपित बिंदु से वापस बदला गया।',
})}
</p>
<p className="control-panel-note">
{t({ en: 'Coordinate system', ja: '座標系', 'es-419': 'Sistema de coordenadas', de: 'Koordinatensystem', th: 'ระบบพิกัด', hi: 'निर्देशांक प्रणाली' })}: {holder ? formatSystem(holder.coordinateSystem) : unavailable}
</p>
<ProjectionLines label={t({ en: 'Center', ja: '中心', 'es-419': 'Centro', de: 'Mittelpunkt', th: 'จุดกึ่งกลาง', hi: 'केंद्र' })} point={center} holder={holder} unavailable={unavailable} />
<ProjectionLines label={t({ en: 'Tapped', ja: 'タップ地点', 'es-419': 'Punto tocado', de: 'Angetippt', th: 'จุดที่แตะ', hi: 'टैप किया गया बिंदु' })} point={tapped} holder={holder} unavailable={unavailable} />
</ControlPanel>
);
}

export function ProjectedCoordinatesPage() {
const [state, setState] = useState<MapViewStateInterface<MapDesignTypeInterface<unknown>> | null>(null);
const [center, setCenter] = useState<GeoPoint | null>(null);
const [tapped, setTapped] = useState<GeoPoint | null>(null);
// ホルダは地図が出来てから付く。描画のたびに読むと、付いた後に描き直しが
// 起きず取り残されるので、地図の通知で取り直して state に持つ。
const [holder, setHolder] = useState<Holder | null>(null);
const [markers, setMarkers] = useState<MarkerState[]>([]);
// 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 (
<MapViewContainer
initialCamera={INIT_CAMERA}
onStateReady={setState}
onCameraMove={refresh}
onCameraMoveEnd={refresh}
onMapClick={point => {
const current = state?.getMapViewHolder() ?? null;
setHolder(current);
setTapped(point);
setMarkers(dropMarkers(point, current));
}}
>
{markers.map(marker => <Marker key={marker.id} state={marker} />)}
<ProjectedCoordinatesContent holder={holder} center={center} tapped={tapped} />
</MapViewContainer>
);
}
2 changes: 2 additions & 0 deletions examples/basic/src/samples/documentation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -56,6 +57,7 @@ const DOCUMENTATION: Record<string, SamplePageDoc> = {
'fly-to': flyTo,
tilt,
'visible-region': visibleRegion,
'projected-coordinates': projectedCoordinates,
'camera-sync': cameraSync,
marker,
'marker-label-position': markerLabelPosition,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { SamplePageDoc } from '../types';

const doc: SamplePageDoc = {
helpers: `function ProjectedValues({ holder, point }: { holder: MapViewHolder<unknown, unknown> | null; point: GeoPoint | null }) {
if (!holder || !point) return <p>Tap the map to project the tapped point.</p>;
const projected = holder.toProjectedPoint(point);
const back = projected ? holder.fromProjectedPoint(projected) : null;
return <pre>{JSON.stringify({
coordinateSystem: holder.coordinateSystem,
projected,
back: back?.toUrlValue(6),
}, null, 2)}</pre>;
}`,
code: `<MapViewContainer
state={mapViewState}
onMapClick={point => setTapped(point)}
/>

<ProjectedValues holder={mapViewState.getMapViewHolder()} point={tapped} />`,
state: `const [tapped, setTapped] = useState<GeoPoint | null>(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;
11 changes: 11 additions & 0 deletions examples/basic/src/samples/sampleIntro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ const INTROS: Record<string, SampleIntro> = 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',
{
Expand Down
1 change: 1 addition & 0 deletions examples/basic/src/samples/sampleRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
Loading