487 lines
13 KiB
TypeScript
487 lines
13 KiB
TypeScript
import OSM from 'ol/source/OSM';
|
|
import XYZ from 'ol/source/XYZ';
|
|
import { Component, createEffect, createSignal, For, Show } from 'solid-js';
|
|
import {
|
|
I18nContext,
|
|
createI18nContext,
|
|
useI18n,
|
|
} from '@solid-primitives/i18n';
|
|
|
|
import style from './MapTileProvider.module.css';
|
|
import LayersIcon from '@suid/icons-material/Layers';
|
|
import CloseIcon from '@suid/icons-material/Close';
|
|
import {
|
|
FormControlLabel,
|
|
IconButton,
|
|
Radio,
|
|
RadioGroup,
|
|
} from '@suid/material';
|
|
import { useNavigate, useParams } from '@solidjs/router';
|
|
import Dialog from '../dialog';
|
|
import Tree from '../tree';
|
|
import { createCachedSignal } from '../../workers/cached-signals';
|
|
import dispatch from '../../workers/dispatcher-main';
|
|
import getUri from '../../lib/ids';
|
|
import { Feature } from 'ol';
|
|
|
|
import {
|
|
overlayCategories,
|
|
overlayDefinitions,
|
|
} from '../overlays/overlay-definitions';
|
|
|
|
const id = getUri('overlays', undefined);
|
|
|
|
interface TileProvider {
|
|
name: string;
|
|
language: string;
|
|
source: XYZ;
|
|
}
|
|
|
|
type TileProviders = {
|
|
[key: string]: TileProvider;
|
|
};
|
|
|
|
export const mapTileProviders: TileProviders = {
|
|
osm: {
|
|
name: 'Open Street Map',
|
|
language: 'int',
|
|
source: new OSM(),
|
|
},
|
|
osmfr: {
|
|
name: 'Open Street Map France',
|
|
language: 'fr',
|
|
source: new XYZ({
|
|
minZoom: 0,
|
|
maxZoom: 20,
|
|
url: 'https://{a-c}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
|
|
}),
|
|
},
|
|
otm: {
|
|
name: 'Open Topo Map',
|
|
language: 'int',
|
|
source: new XYZ({
|
|
minZoom: 0,
|
|
maxZoom: 16,
|
|
url: 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png',
|
|
}),
|
|
},
|
|
cyclosm: {
|
|
name: 'CyclOSM',
|
|
language: 'int',
|
|
source: new XYZ({
|
|
minZoom: 0,
|
|
maxZoom: 19,
|
|
url: 'https://{a-c}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png',
|
|
}),
|
|
},
|
|
//https://b.tile.openstreetmap.fr/openriverboatmap/20/535762/382966.png
|
|
openriverboatmap: {
|
|
name: 'Open River Boat Map',
|
|
language: 'int',
|
|
source: new XYZ({
|
|
minZoom: 0,
|
|
maxZoom: 19,
|
|
url: 'https://{a-c}.tile.openstreetmap.fr/openriverboatmap/{z}/{x}/{y}.png',
|
|
}),
|
|
},
|
|
rasterIgnEs: {
|
|
name: 'Cartografía Ráster de España del IGN',
|
|
language: 'es',
|
|
source: new XYZ({
|
|
minZoom: 0,
|
|
maxZoom: 19,
|
|
url: 'http://www.ign.es/wmts/mapa-raster?request=getTile&layer=MTN&TileMatrixSet=GoogleMapsCompatible&TileMatrix={z}&TileCol={x}&TileRow={y}&format=image/jpeg',
|
|
}),
|
|
},
|
|
pnoaMaEs: {
|
|
name: 'Ortoimágenes de España',
|
|
language: 'es',
|
|
source: new XYZ({
|
|
minZoom: 0,
|
|
maxZoom: 19,
|
|
url: 'http://www.ign.es/wmts/pnoa-ma?request=getTile&layer=MTN&TileMatrixSet=GoogleMapsCompatible&TileMatrix={z}&TileCol={x}&TileRow={y}&format=image/jpeg',
|
|
}),
|
|
},
|
|
};
|
|
|
|
type FeatureSubTypes = Array<string> | '*';
|
|
type FeatureTypes = Record<string, FeatureSubTypes>;
|
|
type Overlay = {
|
|
selected: boolean;
|
|
highlighted: FeatureTypes;
|
|
hidden: FeatureTypes;
|
|
};
|
|
type Overlays = Record<string, Overlay>;
|
|
|
|
const defaultOverlays: Overlays = {
|
|
none: { selected: true, highlighted: { none: [] }, hidden: {} },
|
|
hiking: {
|
|
selected: false,
|
|
highlighted: {
|
|
none: [],
|
|
},
|
|
hidden: { none: [] },
|
|
},
|
|
};
|
|
|
|
// type OverlayDefinition = Record<string, FeatureTypes>;
|
|
// type OverlayDefinitions = Record<string, OverlayDefinition>;
|
|
|
|
// export const overlayDefinitions: OverlayDefinitions = {
|
|
// none: {},
|
|
// hiking: {
|
|
// none: {},
|
|
// sleeping: {
|
|
// tourism: [
|
|
// 'hotel',
|
|
// 'alpine_hut',
|
|
// 'apartment',
|
|
// 'camp_site',
|
|
// 'chalet',
|
|
// 'guest_house',
|
|
// 'hostel',
|
|
// 'motel',
|
|
// 'wilderness_hut',
|
|
// ],
|
|
// },
|
|
// drinking: {
|
|
// amenity: ['bar', 'cafe', 'pub', 'drinking_water', 'water_point'],
|
|
// natural: ['spring'],
|
|
// },
|
|
// eating: {
|
|
// amenity: ['fast_food', 'pub', 'restaurant'],
|
|
// shop: [
|
|
// 'bakery',
|
|
// 'butcher',
|
|
// 'cheese',
|
|
// 'chocolate',
|
|
// 'convenience',
|
|
// 'dairy',
|
|
// 'farm',
|
|
// 'greengrocer',
|
|
// 'health_food',
|
|
// 'pastry',
|
|
// 'seafood',
|
|
// 'department_store',
|
|
// 'supermarket',
|
|
// ],
|
|
// },
|
|
// health: {
|
|
// amenity: ['doctors', 'hospital', 'pharmacy'],
|
|
// },
|
|
// security: {
|
|
// amenity: ['police', 'fire_station'],
|
|
// },
|
|
// dayToday: {
|
|
// amenity: ['waste_basket', 'waste_disposal'],
|
|
// shop: ['laundry'],
|
|
// },
|
|
// naturalSites: {
|
|
// tourism: ['viewpoint'],
|
|
// natural: '*',
|
|
// },
|
|
// },
|
|
// };
|
|
|
|
const getOverlays = createCachedSignal({
|
|
id,
|
|
method: 'getOverlays',
|
|
defaultOverlays,
|
|
}) as () => Overlays;
|
|
|
|
const currentOverlayKey = () =>
|
|
getOverlays()
|
|
? Object.keys(getOverlays()).filter((key) => getOverlays()[key].selected)[0]
|
|
: 'none';
|
|
|
|
const currentOverlay = () =>
|
|
getOverlays() ? getOverlays()[currentOverlayKey()] : {};
|
|
|
|
const overlayCategoriesPlusNone = { none: [], ...overlayCategories };
|
|
|
|
export const currentCategory = () =>
|
|
currentOverlayKey() === 'none'
|
|
? []
|
|
: ['none', ...overlayCategoriesPlusNone[currentOverlayKey()]];
|
|
|
|
const currentOverlayHighlightedKey = () =>
|
|
currentOverlay() && currentOverlay().highlighted
|
|
? currentOverlay().highlighted
|
|
: 'none';
|
|
|
|
export const currentOverlayHighlightedDefinition = () =>
|
|
currentCategory()[currentOverlayHighlightedKey()];
|
|
|
|
export const getAllPoiTypes = () => {
|
|
let result = new Set();
|
|
Object.keys(overlayDefinitions).forEach((tagName) => {
|
|
const tag = overlayDefinitions[tagName];
|
|
result = new Set([...result, ...Object.keys(tag)]);
|
|
});
|
|
return [...result];
|
|
};
|
|
|
|
export const highlightedTags = () => {
|
|
let result = {};
|
|
Object.keys(overlayDefinitions).forEach((tagName) => {
|
|
let tagValues = [];
|
|
const tag = overlayDefinitions[tagName];
|
|
Object.keys(tag).forEach((tagValue) => {
|
|
const catDef = tag[tagValue];
|
|
Object.keys(catDef).forEach((catName) => {
|
|
if (catName === currentOverlayKey()) {
|
|
const catValue = catDef[catName];
|
|
if (catValue[currentOverlay()?.highlighted]) {
|
|
tagValues = [...tagValues, tagValue];
|
|
}
|
|
}
|
|
});
|
|
});
|
|
if (tagValues.length > 0) {
|
|
result[tagName] = tagValues;
|
|
}
|
|
});
|
|
|
|
// console.log({
|
|
// caller: 'MapTileProviders / highlightedTags',
|
|
// result,
|
|
// currentOverlayKey: currentOverlayKey(),
|
|
// currentOverlay: currentOverlay(),
|
|
// });
|
|
return result;
|
|
};
|
|
|
|
// createEffect(() => {
|
|
// highlightedTags = {};
|
|
// Object.keys(overlayDefinitions).forEach((tagName) => {
|
|
// let tagValues = [];
|
|
// const tag = overlayDefinitions[tagName];
|
|
// Object.keys(tag).forEach((tagValue) => {
|
|
// const catDef = tag[tagValue];
|
|
// Object.keys(catDef).forEach((catName) => {
|
|
// if (catName === currentOverlayKey()) {
|
|
// const catValue = catDef[catName];
|
|
// if (catValue[currentOverlay()?.highlighted]) {
|
|
// tagValues = [...tagValues, tagValue];
|
|
// }
|
|
// }
|
|
// });
|
|
// });
|
|
// if (tagValues.length > 0) {
|
|
// highlightedTags[tagName] = tagValues;
|
|
// }
|
|
// });
|
|
|
|
// console.log({
|
|
// caller: 'MapTileProviders / createEffect',
|
|
// highlightedTags,
|
|
// currentOverlayKey: currentOverlayKey(),
|
|
// currentOverlay: currentOverlay(),
|
|
// });
|
|
// });
|
|
|
|
export const getHighlightedTagValue = (feature: Feature) => {
|
|
let result = false;
|
|
|
|
Object.keys(highlightedTags()).every((tagName) => {
|
|
const value = feature.get(tagName);
|
|
const highlightedValues = highlightedTags()[tagName];
|
|
// console.log({
|
|
// caller: 'MapTileProviders / highlightedTag / found',
|
|
// feature,
|
|
// feature_values: feature.values_,
|
|
// highlightedTags,
|
|
// tagName,
|
|
// tag_value: value,
|
|
// highlightedValues,
|
|
// });
|
|
|
|
if (value !== undefined && highlightedValues.includes(value)) {
|
|
result = value;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
// console.log({
|
|
// caller: 'MapTileProviders / highlightedTag / result',
|
|
// feature,
|
|
// feature_values: feature.values_,
|
|
// highlightedTags,
|
|
// result,
|
|
// });
|
|
return result;
|
|
};
|
|
|
|
export const isHighlighted = (feature: Feature) => {
|
|
return !!getHighlightedTagValue(feature);
|
|
};
|
|
|
|
export const getTagValue = (feature: Feature) => {
|
|
let result = false;
|
|
Object.keys(overlayDefinitions).every((tagName) => {
|
|
const value = feature.get(tagName);
|
|
const tagValues = overlayDefinitions[tagName];
|
|
|
|
if (value !== undefined && Object.keys(tagValues).includes(value)) {
|
|
// console.log({
|
|
// caller: 'MapTileProviders / tagValue / found',
|
|
// feature,
|
|
// feature_values: feature.values_,
|
|
// highlightedTags,
|
|
// tagName,
|
|
// tag_value: value,
|
|
// highlightedValues,
|
|
// });
|
|
result = value;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
// console.log({
|
|
// caller: 'MapTileProviders / isHighlighted / false',
|
|
// feature,
|
|
// feature_values: feature.values_,
|
|
// highlightedTags,
|
|
// });
|
|
return result;
|
|
};
|
|
|
|
const MapTilesProvider: Component<{}> = (props) => {
|
|
const [open, setOpen] = createSignal(false);
|
|
|
|
const navigate = useNavigate();
|
|
|
|
const handleClickOpen = () => {
|
|
setOpen(true);
|
|
};
|
|
const handleClose = () => {
|
|
setOpen(false);
|
|
};
|
|
|
|
const params = useParams();
|
|
|
|
const [t] = useI18n();
|
|
|
|
const handleChange = (ev: any) => {
|
|
navigate(
|
|
`/map/${ev.target.value}/${params.lon}/${params.lat}/${params.zoom}/${params.rotation}`
|
|
);
|
|
setOpen(false);
|
|
};
|
|
|
|
createEffect(() => {
|
|
console.log({
|
|
caller: 'MapTilesProvider',
|
|
overlays: getOverlays(),
|
|
currentOverlayKey: currentOverlayKey(),
|
|
currentOverlay: currentOverlay(),
|
|
currentOverlayDefinition: currentCategory(),
|
|
currentOverlayHighlightedKey: currentOverlayHighlightedKey(),
|
|
});
|
|
});
|
|
|
|
const handleOverlayChange = (ev: any) => {
|
|
const value = ev.target.value;
|
|
console.log({
|
|
caller: 'MapTilesProvider / handleOverlayChange',
|
|
ev,
|
|
value,
|
|
});
|
|
const newOverlays = getOverlays();
|
|
Object.keys(newOverlays).forEach((key) => {
|
|
newOverlays[key].selected = key === value;
|
|
});
|
|
dispatch({
|
|
action: 'putOverlays',
|
|
params: { id, overlays: newOverlays },
|
|
});
|
|
};
|
|
|
|
const handleOverlayHighlightChange = (ev: any) => {
|
|
const value = ev.target.value;
|
|
console.log({
|
|
caller: 'MapTilesProvider / handleOverlayHighlightChange',
|
|
ev,
|
|
value,
|
|
});
|
|
const newOverlays = getOverlays();
|
|
newOverlays[currentOverlayKey()].highlighted = value;
|
|
dispatch({
|
|
action: 'putOverlays',
|
|
params: { id, overlays: newOverlays },
|
|
});
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div class={style.control}>
|
|
<IconButton onClick={handleClickOpen}>
|
|
<LayersIcon />
|
|
</IconButton>
|
|
</div>
|
|
<Dialog
|
|
closeHandler={handleClose}
|
|
open={open()}
|
|
title={t('chooseYourMap')}
|
|
>
|
|
<Tree
|
|
title={t('overlay')}
|
|
content={
|
|
<>
|
|
<RadioGroup
|
|
defaultValue={currentOverlayKey()}
|
|
onChange={handleOverlayChange}
|
|
>
|
|
<For each={Object.keys(overlayCategoriesPlusNone)}>
|
|
{(p: string) => (
|
|
<FormControlLabel value={p} control={<Radio />} label={p} />
|
|
)}
|
|
</For>
|
|
</RadioGroup>
|
|
<Show when={currentCategory().length > 0}>
|
|
<div>---</div>
|
|
<div>Highlight</div>
|
|
<RadioGroup
|
|
defaultValue={currentOverlayHighlightedKey()}
|
|
onChange={handleOverlayHighlightChange}
|
|
>
|
|
<For each={currentCategory()}>
|
|
{(p: string) => (
|
|
<FormControlLabel
|
|
value={p}
|
|
control={<Radio />}
|
|
label={p}
|
|
/>
|
|
)}
|
|
</For>
|
|
</RadioGroup>
|
|
</Show>
|
|
</>
|
|
}
|
|
subTree={undefined}
|
|
></Tree>
|
|
|
|
<Tree
|
|
title={t('baseLayer')}
|
|
content={
|
|
<RadioGroup defaultValue={params.provider} onChange={handleChange}>
|
|
<For each={Object.keys(mapTileProviders)}>
|
|
{(p: string) => (
|
|
<FormControlLabel
|
|
value={p}
|
|
control={<Radio />}
|
|
label={mapTileProviders[p].name}
|
|
/>
|
|
)}
|
|
</For>
|
|
</RadioGroup>
|
|
}
|
|
subTree={undefined}
|
|
></Tree>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default MapTilesProvider;
|