First steps at supporting other map tile servers (#7)
This commit is contained in:
parent
32f147bdb6
commit
ac67a063c2
|
@ -0,0 +1,13 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { IonButton, IonIcon } from '@ionic/react';
|
||||||
|
import { layersOutline } from 'ionicons/icons';
|
||||||
|
const TileServerChooserButton: React.FC<{}> = () => {
|
||||||
|
return (
|
||||||
|
<IonButton id='open-TileServerChooser'>
|
||||||
|
<IonIcon slot='icon-only' icon={layersOutline} />
|
||||||
|
</IonButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TileServerChooserButton;
|
|
@ -0,0 +1,52 @@
|
||||||
|
import {
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonList,
|
||||||
|
IonModal,
|
||||||
|
IonRadio,
|
||||||
|
IonRadioGroup,
|
||||||
|
IonTitle,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { mapActions, MapState } from '../../store/map';
|
||||||
|
import { tileProviders } from './tile';
|
||||||
|
|
||||||
|
const TileServerChooserDialog: React.FC<{}> = () => {
|
||||||
|
|
||||||
|
const tileProvider = useSelector(
|
||||||
|
(state: { map: MapState }) => state.map.scope.tileProvider
|
||||||
|
) as keyof typeof tileProviders;
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const changeHandler = (event: any) => {
|
||||||
|
dispatch(mapActions.setTileProvider(event.detail.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonModal trigger='open-TileServerChooser'>
|
||||||
|
<IonHeader>
|
||||||
|
<IonTitle>Select your map</IonTitle>
|
||||||
|
</IonHeader>
|
||||||
|
<IonContent>
|
||||||
|
<IonList>
|
||||||
|
<IonRadioGroup value={tileProvider} onIonChange={changeHandler}>
|
||||||
|
{Object.keys(tileProviders).map((provider) => {
|
||||||
|
return (
|
||||||
|
<IonItem key={provider}>
|
||||||
|
<IonLabel> {tileProviders[provider].name}</IonLabel>
|
||||||
|
<IonRadio slot='start' value={provider} />
|
||||||
|
</IonItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</IonRadioGroup>
|
||||||
|
</IonList>
|
||||||
|
</IonContent>
|
||||||
|
</IonModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TileServerChooserDialog;
|
|
@ -23,6 +23,8 @@ import GpxImport from './gpx-import';
|
||||||
import Gpxes from './gpxes';
|
import Gpxes from './gpxes';
|
||||||
import GpxRecord from './gpx-record';
|
import GpxRecord from './gpx-record';
|
||||||
import { initDb } from '../../db';
|
import { initDb } from '../../db';
|
||||||
|
import TileServerChooserButton from './TileServerChooserButton';
|
||||||
|
import TileServerChooserDialog from './TileServerChooserDialog';
|
||||||
|
|
||||||
const Map: react.FC<{}> = (props: {}) => {
|
const Map: react.FC<{}> = (props: {}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -48,6 +50,7 @@ const Map: react.FC<{}> = (props: {}) => {
|
||||||
<>
|
<>
|
||||||
<IonContent fullscreen={true}>
|
<IonContent fullscreen={true}>
|
||||||
<IonApp>
|
<IonApp>
|
||||||
|
<TileServerChooserDialog />
|
||||||
<Slippy>
|
<Slippy>
|
||||||
<Whiteboard>
|
<Whiteboard>
|
||||||
<CurrentLocation />
|
<CurrentLocation />
|
||||||
|
@ -62,6 +65,7 @@ const Map: react.FC<{}> = (props: {}) => {
|
||||||
<IonHeader className='ion-no-border' translucent={true}>
|
<IonHeader className='ion-no-border' translucent={true}>
|
||||||
<IonToolbar>
|
<IonToolbar>
|
||||||
<IonButtons slot='end'>
|
<IonButtons slot='end'>
|
||||||
|
<TileServerChooserButton />
|
||||||
<GpxRecord />
|
<GpxRecord />
|
||||||
<GpxImport />
|
<GpxImport />
|
||||||
</IonButtons>
|
</IonButtons>
|
||||||
|
|
|
@ -1,7 +1,103 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { MapState } from '../../store/map';
|
||||||
|
|
||||||
const tileProvider = (zoom: number, x: number, y: number) =>
|
export interface TileProvider {
|
||||||
'https://tile.openstreetmap.org/' + zoom + '/' + x + '/' + y + '.png';
|
name: string;
|
||||||
|
minZoom: number;
|
||||||
|
maxZoom: number;
|
||||||
|
getTileUrl: { (zoom: number, x: number, y: number): string };
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRandomItem = (items: any[]) => {
|
||||||
|
const idx = Math.floor(Math.random() * items.length);
|
||||||
|
return items[idx];
|
||||||
|
};
|
||||||
|
|
||||||
|
const abc = ['a', 'b', 'c'];
|
||||||
|
|
||||||
|
export const tileProviders: any = {
|
||||||
|
osm: {
|
||||||
|
name: 'Open Street Map',
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 19,
|
||||||
|
getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
'https://tile.openstreetmap.org/' + zoom + '/' + x + '/' + y + '.png',
|
||||||
|
},
|
||||||
|
osmfr: {
|
||||||
|
name: 'Open Street Map France',
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 19,
|
||||||
|
getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
'https://' +
|
||||||
|
getRandomItem(abc) +
|
||||||
|
'.tile.openstreetmap.fr/osmfr/' +
|
||||||
|
zoom +
|
||||||
|
'/' +
|
||||||
|
x +
|
||||||
|
'/' +
|
||||||
|
y +
|
||||||
|
'.png',
|
||||||
|
},
|
||||||
|
otm: {
|
||||||
|
name: 'Open Topo Map',
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 19,
|
||||||
|
getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
'https://' +
|
||||||
|
getRandomItem(abc) +
|
||||||
|
'.tile.opentopomap.org/' +
|
||||||
|
zoom +
|
||||||
|
'/' +
|
||||||
|
x +
|
||||||
|
'/' +
|
||||||
|
y +
|
||||||
|
'.png',
|
||||||
|
},
|
||||||
|
cyclosm: {
|
||||||
|
name: 'CyclOSM',
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 19,
|
||||||
|
getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
'https://' +
|
||||||
|
getRandomItem(abc) +
|
||||||
|
'.tile-cyclosm.openstreetmap.fr/cyclosm/' +
|
||||||
|
zoom +
|
||||||
|
'/' +
|
||||||
|
x +
|
||||||
|
'/' +
|
||||||
|
y +
|
||||||
|
'.png',
|
||||||
|
},
|
||||||
|
// cyclosmlite: {
|
||||||
|
// name: 'CyclOSM lite',
|
||||||
|
// minZoom: 0,
|
||||||
|
// maxZoom: 19,
|
||||||
|
// getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
// 'https://' +
|
||||||
|
// getRandomItem(abc) +
|
||||||
|
// '.tile-cyclosm.openstreetmap.fr/cyclosm-lite/' +
|
||||||
|
// zoom +
|
||||||
|
// '/' +
|
||||||
|
// x +
|
||||||
|
// '/' +
|
||||||
|
// y +
|
||||||
|
// '.png',
|
||||||
|
// },
|
||||||
|
// esrisat: {
|
||||||
|
// name: 'ESRI Satellite',
|
||||||
|
// minZoom: 0,
|
||||||
|
// maxZoom: 19,
|
||||||
|
// getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
// 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/' +
|
||||||
|
// zoom +
|
||||||
|
// '/' +
|
||||||
|
// x +
|
||||||
|
// '/' +
|
||||||
|
// y +
|
||||||
|
// '.jpg',
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
|
||||||
const Tile: React.FC<{
|
const Tile: React.FC<{
|
||||||
ix: number;
|
ix: number;
|
||||||
|
@ -16,9 +112,13 @@ const Tile: React.FC<{
|
||||||
y: number;
|
y: number;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
}) => {
|
}) => {
|
||||||
|
const tileProvider = useSelector(
|
||||||
|
(state: { map: MapState }) => state.map.scope.tileProvider
|
||||||
|
) as keyof typeof tileProviders;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
src={tileProvider(props.zoom, props.x, props.y)}
|
src={tileProviders[tileProvider].getTileUrl(props.zoom, props.x, props.y)}
|
||||||
className='tile'
|
className='tile'
|
||||||
alt=''
|
alt=''
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,10 +18,12 @@ export const zoom0 = 18;
|
||||||
export interface MapScope {
|
export interface MapScope {
|
||||||
center: geoPoint;
|
center: geoPoint;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
|
tileProvider: string;
|
||||||
}
|
}
|
||||||
var initialMapScope: MapScope = {
|
var initialMapScope: MapScope = {
|
||||||
center: { lat: -37.8403508, lon: 77.5539501 },
|
center: { lat: -37.8403508, lon: 77.5539501 },
|
||||||
zoom: 15,
|
zoom: 15,
|
||||||
|
tileProvider: 'osm',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Derived properties
|
// Derived properties
|
||||||
|
@ -197,6 +199,9 @@ const mapSlice = createSlice({
|
||||||
name: 'map',
|
name: 'map',
|
||||||
initialState: initialMapState,
|
initialState: initialMapState,
|
||||||
reducers: {
|
reducers: {
|
||||||
|
setTileProvider: (state, action) => {
|
||||||
|
state.scope.tileProvider = action.payload;
|
||||||
|
},
|
||||||
resize: (state) => {
|
resize: (state) => {
|
||||||
evaluateStateFromScope(state);
|
evaluateStateFromScope(state);
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue