Implementing a Marker component.

This commit is contained in:
Eric van der Vlist 2022-11-02 21:27:29 +01:00
parent 925174db79
commit 1ed4875e5e
6 changed files with 107 additions and 19 deletions

View File

@ -36,6 +36,7 @@ import GetLocation from './components/buttons/GetLocation';
import { geoPoint } from './components/map/types'; import { geoPoint } from './components/map/types';
import Back from './components/buttons/Back'; import Back from './components/buttons/Back';
import Forward from './components/buttons/Forward'; import Forward from './components/buttons/Forward';
import Marker from './components/map/Marker';
setupIonicReact(); setupIonicReact();
@ -62,6 +63,24 @@ const App: React.FC = () => {
const [scope, setScope] = useAtom(scopeAtom); const [scope, setScope] = useAtom(scopeAtom);
console.log(`App, scope: ${JSON.stringify(scope)}`); console.log(`App, scope: ${JSON.stringify(scope)}`);
const marker = (
<Marker
coordinates={{ lat: -37.8403508, lon: 77.5539501 }}
icon={
<circle
cx={0}
cy={0}
r={6 / 256}
fill='blue'
opacity='90%'
stroke='white'
strokeWidth={3 / 256}
strokeOpacity='100%'
></circle>
}
key='current'
/>
);
return ( return (
<IonApp> <IonApp>
<IonContent fullscreen={true}> <IonContent fullscreen={true}>
@ -70,6 +89,7 @@ const App: React.FC = () => {
scope={scope} scope={scope}
setScope={debounce(setScope, 1000)} setScope={debounce(setScope, 1000)}
numberOfTiledLayers={5} numberOfTiledLayers={5}
markers={[marker]}
/> />
</IonApp> </IonApp>
</IonContent> </IonContent>

View File

@ -1,4 +1,10 @@
import react from 'react'; import react, {
cloneElement,
JSXElementConstructor,
ReactComponentElement,
ReactElement,
ReactNode,
} from 'react';
import { TileKeyObject } from './types'; import { TileKeyObject } from './types';
@ -6,6 +12,8 @@ import { CoordinateSystem } from './LiveMap';
import { range } from 'lodash'; import { range } from 'lodash';
import tileUri from './uris'; import tileUri from './uris';
import TiledLayer from './TiledLayer'; import TiledLayer from './TiledLayer';
import { ReactComponentOrElement } from '@ionic/react';
import { KeyObject } from 'crypto';
export interface LayerStackProperties { export interface LayerStackProperties {
/** /**
@ -20,6 +28,8 @@ export interface LayerStackProperties {
* The coordinates system * The coordinates system
*/ */
coordinateSystem: CoordinateSystem; coordinateSystem: CoordinateSystem;
/** Markers are non scalable SVG snippets tied to geo location */
markers?: ReactElement<any, string | JSXElementConstructor<any>>[];
} }
/** /**
@ -82,6 +92,7 @@ export const LayerStack: react.FC<LayerStackProperties> = (
<svg width='100%' height='100%' data-testid='layer-stack'> <svg width='100%' height='100%' data-testid='layer-stack'>
<g <g
transform={`translate(${props.coordinateSystem.shift.x}, ${props.coordinateSystem.shift.y}) scale(${props.coordinateSystem.zoom})`} transform={`translate(${props.coordinateSystem.shift.x}, ${props.coordinateSystem.shift.y}) scale(${props.coordinateSystem.zoom})`}
key='tiles'
> >
{ {
// Tiled layers with less detail // Tiled layers with less detail
@ -98,6 +109,23 @@ export const LayerStack: react.FC<LayerStackProperties> = (
getTiledLayer(activeTiledLayer) getTiledLayer(activeTiledLayer)
} }
</g> </g>
{props.markers !== undefined ? (
<g
key='markers'
transform={`translate(${props.coordinateSystem.shift.x}, ${props.coordinateSystem.shift.y}) scale(${props.coordinateSystem.zoom})`}
>
<g
transform={`scale(256) translate(-${props.keyObject.x}, -${props.keyObject.y})`}
>
{props.markers.map((marker) =>
cloneElement(marker, {
keyObject: props.keyObject,
zoom: props.coordinateSystem.zoom,
})
)}
</g>
</g>
) : null}
</svg> </svg>
); );
}; };

View File

@ -1,9 +1,10 @@
import react, { useEffect, useState } from 'react'; import react, { JSXElementConstructor, ReactComponentElement, ReactElement, ReactNode, useEffect, useState } from 'react';
import useDimensions from 'react-cool-dimensions'; import useDimensions from 'react-cool-dimensions';
import { MapScope, Point } from './types'; import { MapScope, Point } from './types';
import Map from './Map'; import Map from './Map';
import Handlers from './Handlers'; import Handlers from './Handlers';
import Marker from './Marker';
import { tileProviders } from './tile-providers'; import { tileProviders } from './tile-providers';
import { lon2tile, lat2tile, tile2lat, tile2long } from '../../lib/geo'; import { lon2tile, lat2tile, tile2lat, tile2long } from '../../lib/geo';
@ -39,6 +40,8 @@ export interface LiveMapProperties {
numberOfTiledLayers?: number; numberOfTiledLayers?: number;
/** If provided, a function to call when the scope is updated. */ /** If provided, a function to call when the scope is updated. */
setScope?: (scope: MapScope) => void; setScope?: (scope: MapScope) => void;
/** Markers are non scalable SVG snippets tied to geo location */
markers?: ReactElement<any, string | JSXElementConstructor<any>>[];
} }
/** /**
@ -132,7 +135,11 @@ export const LiveMap: react.FC<LiveMapProperties> = (
return ( return (
<div style={{ width: '100%', height: '100%' }} ref={observe}> <div style={{ width: '100%', height: '100%' }} ref={observe}>
<Handlers transformMap={transform} /> <Handlers transformMap={transform} />
<Map scope={scope} numberOfTiledLayers={props.numberOfTiledLayers} /> <Map
scope={scope}
numberOfTiledLayers={props.numberOfTiledLayers}
markers={props.markers}
/>
</div> </div>
); );
}; };

View File

@ -1,7 +1,9 @@
import react from 'react'; import react, { JSXElementConstructor, ReactElement, ReactNode } from 'react';
import useDimensions from 'react-cool-dimensions'; import useDimensions from 'react-cool-dimensions';
import { Point, MapScope } from './types'; import { Point, MapScope } from './types';
import Marker from './Marker';
import LayerStack from './LayerStack'; import LayerStack from './LayerStack';
import { tileProviders } from './tile-providers'; import { tileProviders } from './tile-providers';
import { lon2tile, lat2tile } from '../../lib/geo'; import { lon2tile, lat2tile } from '../../lib/geo';
@ -9,6 +11,8 @@ import { lon2tile, lat2tile } from '../../lib/geo';
export interface MapProperties { export interface MapProperties {
scope: MapScope; scope: MapScope;
numberOfTiledLayers?: number; numberOfTiledLayers?: number;
/** Markers are non scalable SVG snippets tied to geo location */
markers?: ReactElement<any, string | JSXElementConstructor<any>>[];
} }
/** /**
@ -58,6 +62,7 @@ export const Map: react.FC<MapProperties> = (props: MapProperties) => {
}, },
zoom: relativeScale, zoom: relativeScale,
}} }}
markers={props.markers}
/> />
</div> </div>
); );

View File

@ -0,0 +1,26 @@
import { KeyObject } from 'crypto';
import react, { ReactNode } from 'react';
import { lat2tile, lon2tile } from '../../lib/geo';
import { CoordinateSystem } from './LiveMap';
import { geoPoint, TileKeyObject } from './types';
export interface MarkerProperties {
coordinates: geoPoint;
icon: ReactNode;
keyObject?: TileKeyObject;
zoom?: number;
}
export const Marker: react.FC<MarkerProperties> = (props: MarkerProperties) => {
if (props.keyObject === undefined || props.zoom === undefined) return null;
const x = lon2tile(props.coordinates.lon, props.keyObject.zoomLevel);
const y = lat2tile(props.coordinates.lat, props.keyObject.zoomLevel);
return (
<g transform={`translate(${x}, ${y}) scale(${1 / props.zoom})`}>
{props.icon}
</g>
);
};
export default Marker;

View File

@ -1,4 +1,4 @@
import react, { useRef } from 'react'; import react, { ReactNode, useRef } from 'react';
import TileSet from './TileSet'; import TileSet from './TileSet';
import { Point, TileKeyObject } from './types'; import { Point, TileKeyObject } from './types';
import { CoordinateSystem } from './LiveMap'; import { CoordinateSystem } from './LiveMap';
@ -75,20 +75,22 @@ export const TiledLayer: react.FC<TiledLayerProperties> = (
}; };
} }
return ( return (
<g <>
transform={`scale(${props.zoom}) translate(${props.shift.x}, ${props.shift.y})`} <g
ref={g} transform={`scale(${props.zoom}) translate(${props.shift.x}, ${props.shift.y})`}
> ref={g}
<TileSet >
keyObject={{ <TileSet
provider: props.keyObject.provider, keyObject={{
zoomLevel: props.keyObject.zoomLevel, provider: props.keyObject.provider,
x: 0, zoomLevel: props.keyObject.zoomLevel,
y: 0, x: 0,
}} y: 0,
viewPort={viewPort} }}
/> viewPort={viewPort}
</g> />
</g>
</>
); );
}; };