First steps at supporting other map tile servers (#7)

This commit is contained in:
Eric van der Vlist 2022-09-27 12:01:26 +02:00
parent 32f147bdb6
commit ac67a063c2
5 changed files with 177 additions and 3 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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>

View File

@ -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=''
/> />

View File

@ -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);
}, },