dyomedea/src/components/map-tile-provider/MapTileProvider.tsx

497 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,
legacyOverlayDefinitions,
} from '../overlays/overlay-definitions';
import { cloneDeep } from 'lodash';
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 defaultOverlayContent = {
selected: false,
highlighted: {
none: [],
},
hidden: { none: [] },
};
const defaultOverlays: Overlays = {
none: { selected: true, highlighted: { none: [] }, hidden: {} },
};
// type OverlayDefinition = Record<string, FeatureTypes>;
// type OverlayDefinitions = Record<string, OverlayDefinition>;
// export const legacyOverlayDefinitions: 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(legacyOverlayDefinitions).forEach((tagName) => {
const tag = legacyOverlayDefinitions[tagName];
result = new Set([...result, ...Object.keys(tag)]);
});
return [...result];
};
export const highlightedTags = () => {
let result = {};
Object.keys(legacyOverlayDefinitions).forEach((tagName) => {
let tagValues = [];
const tag = legacyOverlayDefinitions[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(legacyOverlayDefinitions).forEach((tagName) => {
// let tagValues = [];
// const tag = legacyOverlayDefinitions[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(legacyOverlayDefinitions).every((tagName) => {
const value = feature.get(tagName);
const tagValues = legacyOverlayDefinitions[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 getOverlaysAddingSelectedIfMissing = (key: string) => {
const overlays = getOverlays();
if (!Object.keys(overlays).includes(key)) {
overlays[key] = cloneDeep(defaultOverlayContent);
}
return overlays;
};
const handleOverlayChange = (ev: any) => {
const value = ev.target.value;
console.log({
caller: 'MapTilesProvider / handleOverlayChange',
ev,
value,
});
const newOverlays = getOverlaysAddingSelectedIfMissing(value);
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;