import { Fill, Text, Icon, Stroke, Style, Circle } from 'ol/style'; import startIcon from '../../icons/flag-start-b-svgrepo-com-green.svg'; import finishIcon from '../../icons/flag-finish-b-o-svgrepo-com-red.svg'; import wptIcon from '../../icons/location-pin-svgrepo-com-green.svg'; import houseIcon from '../../icons/house-svgrepo-com.svg'; import houseFlatIcon from '../../icons/houseFlat-svgrepo-com.svg'; import campingIcon from '../../icons/camping-14-svgrepo-com.svg'; import farmPigIcon from '../../icons/farm-pig-svgrepo-com.svg'; import cheeseIcon from '../../icons/cheese-svgrepo-com.svg'; import trainIcon from '../../icons/train-svgrepo-com.svg'; import picnicIcon from '../../icons/picnic-svgrepo-com.svg'; import caveIcon from '../../icons/cave-entrance-svgrepo-com.svg'; import leftArrowIcon from '../../icons/right-arrow-svgrepo-com.svg'; import blackArrowheadPointingUp from '../../icons/black-arrowhead-pointing-up-svgrepo-com.svg'; import wptIconSel from '../../icons/location-pin-svgrepo-com-red.svg'; import { Feature } from 'ol'; import memoize from 'memoizee'; import { getMap, getState } from '../map'; import { Point } from 'ol/geom'; import { Coordinate } from 'ol/coordinate'; import { createDefaultStyle } from 'ol/style/Style'; import osmIcons, { highlight } from './osm-icons'; import { indexOf } from 'lodash'; interface StyleParameters { type: string; isSelected: boolean; } const icons = { house: { src: houseIcon, scale: 1 / 15, opacity: 0.9, anchor: [0.5, 1], }, houseFlat: { src: houseFlatIcon, scale: 1 / 15, opacity: 0.9, anchor: [0.5, 1], }, camping: { src: campingIcon, scale: 2, opacity: 0.9, anchor: [0.5, 1], }, farmPig: { src: farmPigIcon, scale: 1 / 12, opacity: 0.9, anchor: [0.5, 1], }, cheese: { src: cheeseIcon, scale: 3 / 4, opacity: 0.9, anchor: [0.5, 1], }, train: { src: trainIcon, scale: 1 / 10, opacity: 0.9, anchor: [0.5, 1], }, picnic: { src: picnicIcon, scale: 1 / 15, opacity: 0.9, anchor: [0.5, 1], }, cave: { src: caveIcon, scale: 1 / 7, opacity: 0.9, anchor: [0.5, 1], }, }; const wptIconObj = { src: wptIcon, scale: 0.1, opacity: 0.9, anchor: [0.5, 1], }; const textStroke = new Stroke({ color: '#fff', width: 3, }); const textStrokeSel = new Stroke({ color: 'blue', width: 2, }); const textFill = new Fill({ color: '#000', }); const textFillSel = new Fill({ color: 'red', }); const trksegStroke = new Stroke({ color: [11, 16, 71, 0.8], width: 3, }); const trksegStrokeSel = new Stroke({ color: 'red', width: 3, }); const rteStroke = new Stroke({ color: [18, 71, 11, 0.8], width: 3, }); const rteStrokeSel = new Stroke({ color: 'red', width: 3, }); const circleFill = new Fill({ color: 'rgba(255,255,255,0.4)', }); const circleStroke = new Stroke({ color: '#3399CC', width: 1.25, }); const poiTextFill = new Fill({ color: 'white', }); const circle = new Circle({ fill: circleFill, stroke: circleStroke, radius: 5, }); const replacer = (key: string, value: any) => { if (key === 'feature' && typeof value === 'object') { return { id: value.get('id'), rev: value.getRevision() }; } return value; }; const normalizer = (params: any) => { const key = JSON.stringify(params, replacer); // console.log({ caller: 'getStyle / normalizer', key }); return key; }; const memoizeOptions = { length: 1, normalizer, max: 1024000, }; const zoom = () => Math.floor(getState().zoom); const styles = { wpt: { getParameters: (feature: Feature) => { const minZoom = feature.get('extensions')?.['dyo:minZoom']; return { isSelected: feature.get('isSelected') ?? false, text: feature.get('name'), customIcon: icons[feature.get('sym') as keyof typeof icons], hidden: minZoom && zoom() < minZoom, }; }, getStyle: memoize((params: any) => { console.log({ caller: 'getStyle', params }); const { isSelected, text, customIcon, hidden } = params; if (hidden) { return null; } const icon = customIcon ?? wptIconObj; return new Style({ image: new Icon(icon), text: new Text({ font: '16px Calibri,sans-serif', text: text, fill: isSelected ? textFillSel : textFill, stroke: isSelected ? textStrokeSel : textStroke, offsetY: -40, }), }); }, memoizeOptions), }, trkseg: { getParameters: (feature: Feature) => { return { isSelected: feature.get('isSelected') ?? false, feature: zoom() >= 7 ? feature : undefined, zoom: zoom() >= 7 ? Math.floor(zoom()) : undefined, }; }, getStyle: memoize((params: any) => { console.log({ caller: 'getStyle', params }); const { isSelected, feature, zoom } = params; const styles = [ new Style({ stroke: isSelected ? trksegStrokeSel : trksegStroke }), ]; if (feature) { const map = getMap(); const geometry = feature.getGeometry(); const coordinates = geometry.getCoordinates(); let start = coordinates[0]; let startPixels = map?.getPixelFromCoordinate(start); coordinates.slice(1).forEach((end: Coordinate) => { const endPixels = map?.getPixelFromCoordinate(end); if ( startPixels !== undefined && endPixels !== undefined && Math.sqrt( (startPixels[0] - endPixels[0]) ** 2 + (startPixels[1] - endPixels[1]) ** 2 ) > 80 ) { const dx = end[0] - start[0]; const dy = end[1] - start[1]; const rotation = Math.atan2(dy, dx) - Math.PI / 2; styles.push( new Style({ geometry: new Point(end), image: new Icon({ src: blackArrowheadPointingUp, scale: 1 / 20, anchor: [0.5, 0.5], rotateWithView: true, rotation: -rotation, }), }) ); startPixels = endPixels; start = end; } }); } return styles; }, memoizeOptions), }, rte: { getParameters: (feature: Feature) => { return { isSelected: feature.get('isSelected') ?? false, }; }, getStyle: memoize((params: any) => { console.log({ caller: 'getStyle', params }); const { isSelected } = params; return new Style({ stroke: isSelected ? rteStrokeSel : rteStroke }); }, memoizeOptions), }, route: { getParameters: (feature: Feature) => { return { isSelected: feature.get('isSelected') ?? false, name: feature.get('name'), }; }, getStyle: memoize((params: any) => { console.log({ caller: 'getStyle', params }); const { isSelected, name } = params; return new Style({ stroke: isSelected ? routeStrokeSel : routeStroke, text: new Text({ text: name, font: 'bold 14px "Open Sans", "Arial Unicode MS", "sans-serif"', placement: 'line', padding: [2, 2, 2, 2], fill: new Fill({ color: 'black', }), }), }); }, memoizeOptions), }, way: { strokes: { iwn: new Stroke({ color: [174, 33, 219, 0.8], width: 6, }), nwn: new Stroke({ color: [174, 33, 219, 0.8], width: 5, }), rwn: new Stroke({ color: [174, 33, 219, 0.8], width: 5, lineDash: [10, 10], }), lwn: new Stroke({ color: [174, 33, 219, 0.8], width: 3, lineDash: [10, 10], }), }, getParameters: (feature: Feature) => { return { isSelected: feature.get('isSelected') ?? false, name: feature.get('name'), network: feature.get('network'), }; }, getStyle: memoize((params: any) => { // console.log({ caller: 'getStyle', params }); const { isSelected, name, network } = params; return new Style({ stroke: styles.way.strokes[network], text: new Text({ text: name, font: 'bold 14px "Open Sans", "Arial Unicode MS", "sans-serif"', placement: 'line', padding: [2, 2, 2, 2], fill: new Fill({ color: 'black', }), }), }); }, memoizeOptions), }, poi: { getParameters: (feature: Feature) => { const klass: string = feature.get('class'); const isHighlighted = highlight.hasOwnProperty(klass); return { isSelected: feature.get('isSelected') ?? false, name: feature.get('name'), klass, isHighlighted, isTextHidden: zoom() < 19, // isHidden: !isHighlighted && zoom() < 16, isHidden: true, }; }, getStyle: memoize((params: any) => { // console.log({ caller: 'getStyle', params }); const { isSelected, name, klass, isHidden, isTextHidden, isHighlighted } = params; if (isHidden) { return null; } const icon = osmIcons[klass]; if (icon === undefined) { return undefined; } return new Style({ image: new Icon({ src: icon, scale: isHighlighted ? 2 : 1, opacity: 1, color: isHighlighted ? 'red' : 'black', // anchor: [0, 0], }), text: name && !isTextHidden ? new Text({ text: name, font: 'bold 14px "Open Sans", "Arial Unicode MS", "sans-serif"', offsetY: +40, padding: [0, 0, 0, 0], fill: new Fill({ color: 'black', }), backgroundFill: poiTextFill, }) : undefined, }); }, memoizeOptions), }, }; export const style = (feature: Feature, resolution: number) => { const type = ( feature.get('type') !== undefined ? feature.get('type') : feature.type_ ) as keyof typeof styles; const styleForType = styles[type]; if (!styleForType) { // console.log({ caller: 'style / default', type, feature, resolution }); return createDefaultStyle(feature, resolution)[0]; } const params = styles[type].getParameters(feature); if (params?.isHidden) { return null; } const getStyle = styles[type].getStyle; const style = getStyle(params); // console.log({ caller: 'style', feature, type, params, style }); if (style === undefined) { return createDefaultStyle(feature, resolution)[0]; } return style; }; export default style;