kopia lustrzana https://github.com/FacilMap/facilmap
Add more shapes (#78)
rodzic
8a557b19c2
commit
699a660a65
|
@ -35,7 +35,7 @@ To edit the details of a marker, select the marker and then click “Edit data
|
|||
* **Colour:** The colour of the marker.
|
||||
* **Size:** The size of the marker (height in pixels). Use the + and − buttons to change the value.
|
||||
* **Icon:** An icon to show inside the marker shape. You can leave it empty (will show a dot), select an icon from the list, or type in a single character (such as `1`).
|
||||
* **Shape:** The shape of the marker, either a drop or a circle.
|
||||
* **Shape:** The shape of the marker.
|
||||
* **Description:** The description of the marker. Will be shown in the marker details. You can format the text using [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
|
||||
|
||||
Click “Save” to save your changes.
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
padding: 0;
|
||||
list-style-type: none;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 37px);
|
||||
grid-template-columns: repeat(5, 37px);
|
||||
overflow-y: auto;
|
||||
|
||||
li {
|
||||
|
|
|
@ -36,10 +36,10 @@
|
|||
}
|
||||
|
||||
createIcons("icons", (icon) => L.FacilMap.getSymbolHtml("#000000", 50, icon));
|
||||
createIcons("icons-circle", (icon) => L.FacilMap.getMarkerHtml("#ccffcc", 50, icon, "circle"));
|
||||
createIcons("icons-circle-highlight", (icon) => L.FacilMap.getMarkerHtml("#ccffcc", 50, icon, "circle", true));
|
||||
createIcons("icons-drop", (icon) => L.FacilMap.getMarkerHtml("#ccffcc", 50, icon, "drop"));
|
||||
createIcons("icons-drop-highlight", (icon) => L.FacilMap.getMarkerHtml("#ccffcc", 50, icon, "drop", true));
|
||||
for (const shape of ["drop", "rectangle-marker", "circle", "rectangle", "diamond", "pentagon", "hexagon", "triangle", "triangle-down", "star"]) {
|
||||
createIcons(`icons-${shape}`, (icon) => L.FacilMap.getMarkerHtml("#ccffcc", 50, icon, shape));
|
||||
createIcons(`icons-${shape}-highlight`, (icon) => L.FacilMap.getMarkerHtml("#ccffcc", 50, icon, shape, true));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Colour, Shape, Symbol } from "facilmap-types";
|
||||
import { Shape, Symbol } from "facilmap-types";
|
||||
import { makeTextColour, quoteHtml } from "facilmap-utils";
|
||||
import L, { Icon } from "leaflet";
|
||||
import { memoize } from "lodash";
|
||||
|
@ -24,49 +24,110 @@ export const symbolList = Object.keys(rawIcons).map((key) => Object.keys(rawIcon
|
|||
export const RAINBOW_STOPS = `<stop offset="0" stop-color="red"/><stop offset="33%" stop-color="#ff0"/><stop offset="50%" stop-color="#0f0"/><stop offset="67%" stop-color="cyan"/><stop offset="100%" stop-color="blue"/>`;
|
||||
|
||||
interface ShapeInfo {
|
||||
svg: string;
|
||||
highlightSvg: string;
|
||||
height: number;
|
||||
/** An SVG path that defines the shape of the marker and has a height of 36px. */
|
||||
path: string;
|
||||
|
||||
/** The width of the SVG path. */
|
||||
width: number;
|
||||
baseX: number;
|
||||
baseY: number;
|
||||
|
||||
/** The X and Y coordinates of the point on the marker that should be aligned with the point on the map.
|
||||
* For a square marker, the bottom center would be [36, 18] and the center would be [18, 18]. */
|
||||
base: [number, number];
|
||||
|
||||
/** The X and Y coordinates of the center of the marker. This is where the symbol will be inserted.
|
||||
* For a square marker, the center would be [18, 18]. */
|
||||
center: [number, number];
|
||||
|
||||
/** The height/width that inserted symbols should have. */
|
||||
symbolSize: number;
|
||||
|
||||
/** Scale factor for the shape itself. If this is 1, a markers that has its size set to 25 will be 25px high. */
|
||||
scale: number;
|
||||
}
|
||||
|
||||
const MARKER_SHAPES: Partial<Record<Shape, ShapeInfo>> = {
|
||||
drop: {
|
||||
svg: (
|
||||
`<path id="drop-%ID%" style="stroke: %BORDER_COLOUR%; stroke-width: 2; stroke-linecap: round; fill: %COLOUR%; clip-path: url(#clip-%ID%)" d="M13 0C4.727 0 0 4.8 0 13.2 0 21.6 11.744 36 13 36c1.256 0 13-14.4 13-22.8S21.273 0 13 0z"/>"/>` +
|
||||
`<clipPath id="clip-%ID%"><use xlink:href="#drop-%ID%"/></clipPath>` + // Don't increase the size by increasing the border: https://stackoverflow.com/a/32162431/242365
|
||||
`<g transform="translate(4, 4) scale(0.75)">%SYMBOL%</g>`
|
||||
),
|
||||
highlightSvg: (
|
||||
`<path id="drop-%ID%" style="stroke: %BORDER_COLOUR%; stroke-width: 6; stroke-linecap: round; fill: %COLOUR%; clip-path: url(#clip-%ID%)" d="M13 0C4.727 0 0 4.8 0 13.2 0 21.6 11.744 36 13 36c1.256 0 13-14.4 13-22.8S21.273 0 13 0z"/>"/>` +
|
||||
`<clipPath id="clip-%ID%"><use xlink:href="#drop-%ID%"/></clipPath>` +
|
||||
`<g transform="translate(4, 4) scale(0.75)">%SYMBOL%</g>`
|
||||
),
|
||||
height: 36,
|
||||
const SHAPE_HEIGHT = 36;
|
||||
const DEFAULT_SHAPE: Shape = "drop";
|
||||
|
||||
const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
|
||||
"drop": {
|
||||
path: "M13 0C4.727 0 0 4.8 0 13.2 0 21.6 11.744 36 13 36c1.256 0 13-14.4 13-22.8S21.273 0 13 0z",
|
||||
width: 26,
|
||||
baseX: 13,
|
||||
baseY: 36,
|
||||
base: [13, 36],
|
||||
center: [13, 13],
|
||||
symbolSize: 18,
|
||||
scale: 1
|
||||
},
|
||||
circle: {
|
||||
svg: (
|
||||
`<circle id="circle-%ID%" style="stroke: %BORDER_COLOUR%; stroke-width: 2; fill: %COLOUR%; clip-path: url(#clip-%ID%)" cx="18" cy="18" r="18" />` +
|
||||
`<clipPath id="clip-%ID%"><use xlink:href="#circle-%ID%"/></clipPath>` +
|
||||
`<g transform="translate(6, 6)">%SYMBOL%</g>`
|
||||
),
|
||||
highlightSvg: (
|
||||
`<circle id="circle-%ID%" style="stroke: %BORDER_COLOUR%; stroke-width: 6; fill: %COLOUR%; clip-path: url(#clip-%ID%)" cx="18" cy="18" r="18" />` +
|
||||
`<clipPath id="clip-%ID%"><use xlink:href="#circle-%ID%"/></clipPath>` +
|
||||
`<g transform="translate(6, 6)">%SYMBOL%</g>`
|
||||
),
|
||||
height: 36,
|
||||
"rectangle-marker": {
|
||||
path: "M2.64 0A2.646 2.646 0 0 0 0 2.646v24.708A2.646 2.646 0 0 0 2.646 30h8.89l1.732 3L15 36l1.732-3 1.732-3h8.89A2.646 2.646 0 0 0 30 27.354V2.646A2.646 2.646 0 0 0 27.354 0H2.646a2.646 2.646 0 0 0-.005 0z",
|
||||
width: 30,
|
||||
base: [15, 36],
|
||||
center: [15, 15],
|
||||
symbolSize: 24,
|
||||
scale: 0.95
|
||||
},
|
||||
"circle": {
|
||||
path: "M36 18a18 18 0 0 1-18 18A18 18 0 0 1 0 18 18 18 0 0 1 18 0a18 18 0 0 1 18 18z",
|
||||
width: 36,
|
||||
baseX: 18,
|
||||
baseY: 18,
|
||||
base: [18, 18],
|
||||
center: [18, 18],
|
||||
symbolSize: 24,
|
||||
scale: 0.85
|
||||
},
|
||||
"rectangle": {
|
||||
path: "M2.646 0h30.708A2.646 2.646 45 0 1 36 2.646v30.708A2.646 2.646 135 0 1 33.354 36H2.646A2.646 2.646 45 0 1 0 33.354V2.646A2.646 2.646 135 0 1 2.646 0z",
|
||||
width: 36,
|
||||
base: [18, 18],
|
||||
center: [18, 18],
|
||||
symbolSize: 28,
|
||||
scale: 0.8
|
||||
},
|
||||
"diamond": {
|
||||
path: "M19.382.573l16.045 16.045a1.955 1.955 90 0 1 0 2.764L19.382 35.427a1.955 1.955 180 0 1-2.764 0L.573 19.382a1.955 1.955 90 0 1 0-2.764L16.618.573a1.955 1.955 0 0 1 2.764 0z",
|
||||
width: 36,
|
||||
base: [18, 18],
|
||||
center: [18, 18],
|
||||
symbolSize: 18,
|
||||
scale: 0.9
|
||||
},
|
||||
"pentagon": {
|
||||
path: "M20.078.347l17.17 12.371a1.808 1.808 71.97 0 1 .662 2.03l-6.562 19.995A1.826 1.826 144.093 0 1 29.612 36l-21.237-.006a1.826 1.826 35.94 0 1-1.735-1.258L.09 14.738a1.808 1.808 108.063 0 1 .662-2.03L17.93.346a1.837 1.837.017 0 1 2.148 0z",
|
||||
width: 38,
|
||||
base: [19, 20],
|
||||
center: [19, 20],
|
||||
symbolSize: 20,
|
||||
scale: 0.85
|
||||
},
|
||||
"hexagon": {
|
||||
path: "M14.833 35.692L1.151 27.826A2.305 2.29 0 0 1 0 25.842l.013-15.71A2.305 2.29 0 0 1 1.167 8.15L14.862.306a2.305 2.29 0 0 1 2.305.002l13.682 7.866A2.305 2.29 0 0 1 32 10.158l-.013 15.71a2.305 2.29 0 0 1-1.154 1.982l-13.695 7.844a2.305 2.29 0 0 1-2.305-.002z",
|
||||
width: 32,
|
||||
base: [16, 18],
|
||||
center: [16, 18],
|
||||
symbolSize: 22,
|
||||
scale: 0.9
|
||||
},
|
||||
"triangle": {
|
||||
path: "M.134 34.33L19.976.494a1.013 1.013 179.88 0 1 1.746-.003l20.139 34.014a.986.986 119.818 0 1-.853 1.489L.977 35.809a.982.982 60.327 0 1-.843-1.48z",
|
||||
width: 42,
|
||||
base: [21, 24],
|
||||
center: [21, 24],
|
||||
symbolSize: 16,
|
||||
scale: 0.85
|
||||
},
|
||||
"triangle-down": {
|
||||
path: "M.934 0a.96.96 0 0 0-.805 1.44l19.036 33.12.004.007.824 1.433.828-1.44 19.05-33.112a.96.96 0 0 0-.828-1.44L.958.001H.934z",
|
||||
width: 40,
|
||||
base: [20, 36],
|
||||
center: [20, 12],
|
||||
symbolSize: 16,
|
||||
scale: 0.85
|
||||
},
|
||||
"star": {
|
||||
path: "M19.007 0l5.865 11.851L38 13.758l-9.505 9.218L30.732 36l-11.74-6.154-11.746 6.142 2.25-13.021L0 13.739l13.13-1.893z",
|
||||
width: 38,
|
||||
base: [19, 18],
|
||||
center: [19, 20],
|
||||
symbolSize: 14,
|
||||
scale: 0.9
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -149,52 +210,50 @@ let idCounter = 0;
|
|||
export function getMarkerCode(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): string {
|
||||
const borderColour = makeTextColour(colour, 0.3);
|
||||
const id = `${idCounter++}`;
|
||||
|
||||
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES.drop!;
|
||||
const shapeCode = (highlight ? shapeObj.highlightSvg : shapeObj.svg)
|
||||
.replace(/%BORDER_COLOUR%/g, borderColour)
|
||||
.replace(/%COLOUR%/g, colour == "rainbow" ? `url(#fm-rainbow-${id})` : colour)
|
||||
.replace(/%SYMBOL%/g, getSymbolCode(borderColour, 24, symbol))
|
||||
.replace(/%ID%/g, id);
|
||||
|
||||
const scale = height / shapeObj.height;
|
||||
const colourCode = colour == "rainbow" ? `url(#fm-rainbow-${id})` : colour;
|
||||
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES[DEFAULT_SHAPE];
|
||||
const symbolCode = getSymbolCode(borderColour, shapeObj.symbolSize, symbol);
|
||||
const translateX = `${Math.floor(shapeObj.center[0] - shapeObj.symbolSize / 2)}`;
|
||||
const translateY = `${Math.floor(shapeObj.center[1] - shapeObj.symbolSize / 2)}`;
|
||||
|
||||
return (
|
||||
`<g transform="scale(${scale})">` +
|
||||
`<g transform="scale(${height / SHAPE_HEIGHT})">` +
|
||||
(colour == "rainbow" ? `<defs><linearGradient id="fm-rainbow-${id}" x2="0" y2="100%">${RAINBOW_STOPS}</linearGradient></defs>` : '') +
|
||||
shapeCode +
|
||||
`<path id="shape-${id}" style="stroke: ${borderColour}; stroke-width: ${highlight ? 6 : 2}; stroke-linecap: round; fill: ${colourCode}; clip-path: url(#clip-${id})" d="${quoteHtml(shapeObj.path)}"/>"/>` +
|
||||
`<clipPath id="clip-${id}"><use xlink:href="#shape-${id}"/></clipPath>` + // Don't increase the size by increasing the border: https://stackoverflow.com/a/32162431/242365
|
||||
`<g transform="translate(${translateX}, ${translateY})">${symbolCode}</g>` +
|
||||
`</g>`
|
||||
);
|
||||
}
|
||||
|
||||
export function getMarkerUrl(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): string {
|
||||
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES.drop!;
|
||||
const width = Math.ceil(height * shapeObj.width / shapeObj.height);
|
||||
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES[DEFAULT_SHAPE];
|
||||
const width = Math.ceil(height * shapeObj.width / SHAPE_HEIGHT);
|
||||
return "data:image/svg+xml,"+encodeURIComponent(
|
||||
`<?xml version="1.0" encoding="UTF-8" standalone="no"?>` +
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}" viewBox="0 0 ${shapeObj.width} ${shapeObj.height}" version="1.1">` +
|
||||
getMarkerCode(colour, shapeObj.height, symbol, shape, highlight) +
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}" viewBox="0 0 ${shapeObj.width} ${SHAPE_HEIGHT}" version="1.1">` +
|
||||
getMarkerCode(colour, SHAPE_HEIGHT, symbol, shape, highlight) +
|
||||
`</svg>`
|
||||
);
|
||||
}
|
||||
|
||||
export function getMarkerHtml(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): string {
|
||||
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES.drop!;
|
||||
const width = Math.ceil(height * shapeObj.width / shapeObj.height);
|
||||
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES[DEFAULT_SHAPE];
|
||||
const width = Math.ceil(height * shapeObj.width / SHAPE_HEIGHT);
|
||||
return (
|
||||
`<svg width="${width}" height="${height}" viewBox="0 0 ${shapeObj.width} ${shapeObj.height}">` +
|
||||
getMarkerCode(colour, shapeObj.height, symbol, shape, highlight) +
|
||||
`<svg width="${width}" height="${height}" viewBox="0 0 ${shapeObj.width} ${SHAPE_HEIGHT}">` +
|
||||
getMarkerCode(colour, SHAPE_HEIGHT, symbol, shape, highlight) +
|
||||
`</svg>`
|
||||
);
|
||||
}
|
||||
|
||||
export function getMarkerIcon(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): Icon {
|
||||
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES.drop!;
|
||||
const scale = shapeObj.scale * height / shapeObj.height;
|
||||
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES[DEFAULT_SHAPE];
|
||||
const scale = shapeObj.scale * height / SHAPE_HEIGHT;
|
||||
return L.icon({
|
||||
iconUrl: getMarkerUrl(colour, height, symbol, shape, highlight),
|
||||
iconSize: [shapeObj.width*scale, shapeObj.height*scale],
|
||||
iconAnchor: [Math.round(shapeObj.baseX*scale), Math.round(shapeObj.baseY*scale)],
|
||||
iconSize: [Math.round(shapeObj.width*scale), Math.round(SHAPE_HEIGHT*scale)],
|
||||
iconAnchor: [Math.round(shapeObj.base[0]*scale), Math.round(shapeObj.base[1]*scale)],
|
||||
popupAnchor: [0, -height]
|
||||
});
|
||||
}
|
|
@ -4,7 +4,7 @@ export type ZoomLevel = number;
|
|||
/** Colour in 6-digit hex format without a # */
|
||||
export type Colour = string;
|
||||
export type Symbol = string;
|
||||
export type Shape = "" | "drop" | "circle";
|
||||
export type Shape = string;
|
||||
export type ID = number;
|
||||
export type RouteMode = string;
|
||||
export type Layer = string;
|
||||
|
|
Ładowanie…
Reference in New Issue