From ac67a063c2b5dab63e42447857f5f4036df8391d Mon Sep 17 00:00:00 2001 From: evlist Date: Tue, 27 Sep 2022 12:01:26 +0200 Subject: [PATCH] First steps at supporting other map tile servers (#7) --- .../map/TileServerChooserButton.tsx | 13 +++ .../map/TileServerChooserDialog.tsx | 52 +++++++++ src/components/map/map.tsx | 4 + src/components/map/tile.tsx | 106 +++++++++++++++++- src/store/map.ts | 5 + 5 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 src/components/map/TileServerChooserButton.tsx create mode 100644 src/components/map/TileServerChooserDialog.tsx diff --git a/src/components/map/TileServerChooserButton.tsx b/src/components/map/TileServerChooserButton.tsx new file mode 100644 index 0000000..89e52bc --- /dev/null +++ b/src/components/map/TileServerChooserButton.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import { IonButton, IonIcon } from '@ionic/react'; +import { layersOutline } from 'ionicons/icons'; +const TileServerChooserButton: React.FC<{}> = () => { + return ( + + + + ); +}; + +export default TileServerChooserButton; diff --git a/src/components/map/TileServerChooserDialog.tsx b/src/components/map/TileServerChooserDialog.tsx new file mode 100644 index 0000000..f2eac57 --- /dev/null +++ b/src/components/map/TileServerChooserDialog.tsx @@ -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 ( + + + Select your map + + + + + {Object.keys(tileProviders).map((provider) => { + return ( + + {tileProviders[provider].name} + + + ); + })} + + + + + ); +}; + +export default TileServerChooserDialog; diff --git a/src/components/map/map.tsx b/src/components/map/map.tsx index d5ab94e..a748970 100644 --- a/src/components/map/map.tsx +++ b/src/components/map/map.tsx @@ -23,6 +23,8 @@ import GpxImport from './gpx-import'; import Gpxes from './gpxes'; import GpxRecord from './gpx-record'; import { initDb } from '../../db'; +import TileServerChooserButton from './TileServerChooserButton'; +import TileServerChooserDialog from './TileServerChooserDialog'; const Map: react.FC<{}> = (props: {}) => { const dispatch = useDispatch(); @@ -48,6 +50,7 @@ const Map: react.FC<{}> = (props: {}) => { <> + @@ -62,6 +65,7 @@ const Map: react.FC<{}> = (props: {}) => { + diff --git a/src/components/map/tile.tsx b/src/components/map/tile.tsx index 75e0c57..1a2666f 100644 --- a/src/components/map/tile.tsx +++ b/src/components/map/tile.tsx @@ -1,7 +1,103 @@ import React from 'react'; +import { useSelector } from 'react-redux'; +import { MapState } from '../../store/map'; -const tileProvider = (zoom: number, x: number, y: number) => - 'https://tile.openstreetmap.org/' + zoom + '/' + x + '/' + y + '.png'; +export interface TileProvider { + 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<{ ix: number; @@ -16,9 +112,13 @@ const Tile: React.FC<{ y: number; zoom: number; }) => { + const tileProvider = useSelector( + (state: { map: MapState }) => state.map.scope.tileProvider + ) as keyof typeof tileProviders; + return ( diff --git a/src/store/map.ts b/src/store/map.ts index 055ec8a..e1c91d6 100644 --- a/src/store/map.ts +++ b/src/store/map.ts @@ -18,10 +18,12 @@ export const zoom0 = 18; export interface MapScope { center: geoPoint; zoom: number; + tileProvider: string; } var initialMapScope: MapScope = { center: { lat: -37.8403508, lon: 77.5539501 }, zoom: 15, + tileProvider: 'osm', }; // Derived properties @@ -197,6 +199,9 @@ const mapSlice = createSlice({ name: 'map', initialState: initialMapState, reducers: { + setTileProvider: (state, action) => { + state.scope.tileProvider = action.payload; + }, resize: (state) => { evaluateStateFromScope(state); },