From a7d62c58032915e23bfda2eb36b2dcf0fc5ae4ba Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Fri, 11 Oct 2024 15:00:52 +0800 Subject: feat: add query params for crs --- src/dumbymap.mjs | 10 ++++++++-- src/editor.mjs | 13 ++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 35a4614..593875e 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs @@ -111,11 +111,17 @@ export const markdown2HTML = (container, mdContent) => { * * @param {HTMLElement} container - The container element for the maps * @param {Object} options - Configuration options + * @param {string} options.crs - CRS in EPSG/ESRI code, see epsg.io * @param {number} [options.delay=1000] - Delay before rendering maps (in milliseconds) * @param {Function} options.mapCallback - Callback function to be called after map rendering */ -export const generateMaps = (container, { layouts = [], delay, renderCallback } = {}) => { - /** Prepare Contaner/HTML Holder/Showcase */ +export const generateMaps = (container, { + crs = 'EPSG:4326', + layouts = [], + delay, + renderCallback, +} = {}) => { + /** Prepare Contaner/HTML-Holder/Showcase */ container.classList.add('Dumby') delete container.dataset.layout container.dataset.layout = defaultLayouts[0].name diff --git a/src/editor.mjs b/src/editor.mjs index 04c0502..e12073f 100644 --- a/src/editor.mjs +++ b/src/editor.mjs @@ -14,15 +14,20 @@ import LeaderLine from 'leader-line' */ // Set up Containers {{{ -/** Variables about dumbymap and editor **/ + +/** Variables: page */ const url = new URL(window.location) +const pageParams = url.searchParams +const crs = pageParams.get('crs') ?? 'EPSG:4326' + +/** Variables: dumbymap and editor **/ const context = document.querySelector('[data-mode]') const dumbyContainer = document.querySelector('.DumbyMap') dumbyContainer.dataset.scrollLine = '' const textArea = document.querySelector('.editor textarea') let dumbymap -/** Variables about Reference Style Links in Markdown */ +/** Variables: Reference Style Links in Markdown */ const refLinkPattern = /\[([^\x5B\x5D]+)\]:\s+(\S+)(\s["'](\S+)["'])?/ let refLinks = [] @@ -516,7 +521,9 @@ const menuForEditor = (event, menu) => { const updateDumbyMap = (callback = null) => { markdown2HTML(dumbyContainer, editor.value()) // debounceForMap(dumbyContainer, afterMapRendered) - dumbymap = generateMaps(dumbyContainer) + dumbymap = generateMaps(dumbyContainer, { + crs, + }) // Set onscroll callback const htmlHolder = dumbymap.htmlHolder htmlHolder.onscroll = updateScrollLine(htmlHolder) -- cgit v1.2.3-70-g09d2 From 184b3004d82806f1b31c94701e75585fb8f2721b Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Fri, 11 Oct 2024 15:01:40 +0800 Subject: feat: implement crs in GeoLink Now GeoLink has the following format: geo:,?q=,&xy=,crs= When calling addMarker or updateCamera, only use [lon,lat] format --- package.json | 4 +++- src/dumbyUtils.mjs | 35 +++++++++++++++++++++++------------ src/dumbymap.mjs | 42 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 62 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/package.json b/package.json index 59f4800..a6c7027 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,10 @@ "markdown-it-front-matter": "^0.2.4", "markdown-it-inject-linenumbers": "^0.3.0", "markdown-it-toc-done-right": "^4.2.0", + "ol": "^10.2.1", "plain-draggable": "^2.5.15", - "plain-modal": "^1.0.34" + "plain-modal": "^1.0.34", + "proj4": "^2.12.1" }, "author": "Hsiehg Chin Fan ", "homepage": "https://outdoorsafetylab.github.io/dumbymap", diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs index 170cb16..2e71ee6 100644 --- a/src/dumbyUtils.mjs +++ b/src/dumbyUtils.mjs @@ -1,6 +1,8 @@ import LeaderLine from 'leader-line' import { insideWindow, insideParent } from './utils' +export const coordPattern = /^geo:([-]?[0-9.]+),([-]?[0-9.]+)/ + /** * focusNextMap. * @@ -100,11 +102,12 @@ const getMarkersFromMaps = link => { .filter(map => link.targets ? link.targets.includes(map.id) : true) .map(map => { const renderer = map.renderer - const markerTitle = `${link.targets ?? 'all'}@${link.xy}` + const markerTitle = `${link.targets ?? 'all'}@${link.dataset.xy}` + const lonLat = [Number(link.dataset.lon), Number(link.dataset.lat)] return map.querySelector(`.marker[title="${markerTitle}"]`) ?? renderer.addMarker({ - xy: link.xy, + xy: lonLat, title: markerTitle, type: link.type, }) @@ -138,21 +141,25 @@ const addLeaderLine = (link, target) => { */ export const createGeoLink = (link) => { const url = new URL(link.href) - const xyInParams = url.searchParams.get('xy')?.split(',')?.map(Number) - const xy = xyInParams ?? url?.href - ?.match(/^geo:([-]?[0-9.]+),([-]?[0-9.]+)/) + const params = new URLSearchParams(link.search) + const xyInParams = params.get('xy')?.split(',')?.map(Number) + const [lon, lat] = url.href + ?.match(coordPattern) ?.splice(1) ?.reverse() ?.map(Number) + const xy = xyInParams ?? [lon, lat] if (!xy || isNaN(xy[0]) || isNaN(xy[1])) return false // Geo information in link - link.url = url - link.xy = xy + link.dataset.xy = xy + link.dataset.lon = lon + link.dataset.lat = lat link.classList.add('with-leader-line', 'geolink') - link.targets = link.url.searchParams.get('id')?.split(',') ?? null - link.type = link.url.searchParams.get('type') ?? null + // TODO refactor as data attribute + link.targets = params.get('id')?.split(',') ?? null + link.type = params.get('type') ?? null link.lines = [] @@ -170,7 +177,11 @@ export const createGeoLink = (link) => { link.onclick = (event) => { event.preventDefault() removeLeaderLines(link) - getMarkersFromMaps(link).forEach(updateMapCameraByMarker(link.xy)) + getMarkersFromMaps(link) + .forEach(updateMapCameraByMarker([ + Number(link.dataset.lon), + Number(link.dataset.lat) + ])) } return true } @@ -229,9 +240,9 @@ const removeLeaderLines = link => { * @param {Number[]} xy * @return {Function} function */ -const updateMapCameraByMarker = xy => marker => { +const updateMapCameraByMarker = lonLat => marker => { const renderer = marker.closest('.mapclay')?.renderer - renderer.updateCamera({ center: xy }, true) + renderer.updateCamera({ center: lonLat }, true) } /** diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 593875e..6602f29 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs @@ -9,6 +9,8 @@ import { Layout, SideBySide, Overlay } from './Layout' import * as utils from './dumbyUtils' import * as menuItem from './MenuItem' import PlainModal from 'plain-modal' +import proj4 from 'proj4' +import { register, fromEPSGCode } from 'ol/proj/proj4' /** Selector of special HTML Elements */ const mapBlockSelector = 'pre:has(.language-map)' @@ -59,8 +61,8 @@ export const markdown2HTML = (container, mdContent) => { validate: coordinateRegex, normalize: function (match) { const [, , x, sep, y] = match.text.match(coordinateRegex) - match.url = `geo:${y},${x}?xy=${x},${y}` - match.text = `${x}${sep} ${y}` + match.url = `geo:${y},${x}` + match.text = `${x}${sep}${y}` match.index += match.text.indexOf(x) + 1 return match }, @@ -165,15 +167,43 @@ export const generateMaps = (container, { switchToNextLayout: throttle(utils.switchToNextLayout, 300), }, } - Object.entries(dumbymap.utils).forEach(([util, func]) => { - dumbymap.utils[util] = func.bind(dumbymap) + Object.entries(dumbymap.utils).forEach(([util, value]) => { + if (typeof value === 'function') { + dumbymap.utils[util] = value.bind(dumbymap) + } }) /** Create GeoLinks and DocLinks */ container.querySelectorAll(docLinkSelector) .forEach(utils.createDocLink) - container.querySelectorAll(geoLinkSelector) - .forEach(utils.createGeoLink) + + /** Set CRS and GeoLinks */ + register(proj4) + fromEPSGCode(crs).then(() => { + const transform = proj4(crs, 'EPSG:4326').forward + + Array.from(container.querySelectorAll(geoLinkSelector)) + .map(link => { + // set coordinate as lat/lon in WGS84 + const params = new URLSearchParams(link.search) + const [y, x] = link.href + .match(utils.coordPattern) + .splice(1) + .map(Number) + const [lon, lat] = transform([x, y]) + .map(value => value.toFixed(6)) + link.href = `geo:${lat},${lon}` + + // set query strings + params.set('xy', `${x},${y}`) + params.set('crs', crs) + params.set('q', `${lat},${lon}`) + link.search = params + + return link + }) + .forEach(utils.createGeoLink) + }) /** * mapFocusObserver. observe for map focus -- cgit v1.2.3-70-g09d2 From 2a7a840c25fcdb1653538514fb6609b0dea61b66 Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Fri, 11 Oct 2024 15:17:25 +0800 Subject: feat: add handler for invlid coordinates * prvent default onmouseover/onclick handler * set bg-color of GeoLink as gray * add title to suggest another CRS --- src/css/dumbymap.css | 6 ++++++ src/dumbyUtils.mjs | 6 +++++- src/dumbymap.mjs | 9 ++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/css/dumbymap.css b/src/css/dumbymap.css index 52d0ded..7aff339 100644 --- a/src/css/dumbymap.css +++ b/src/css/dumbymap.css @@ -152,6 +152,12 @@ a[href^='http']:not(:has(img))::after, background-color: #9ee7ea; } + &[data-valid="false"] { + background-color: lightgray; + + opacity: 0.7; + } + &:hover, &.drag { background-image: none; diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs index 2e71ee6..2f92c18 100644 --- a/src/dumbyUtils.mjs +++ b/src/dumbyUtils.mjs @@ -165,6 +165,8 @@ export const createGeoLink = (link) => { // LeaderLine link.onmouseover = () => { + if (link.dataset.valid === 'false') return + const anchors = getMarkersFromMaps(link) anchors .filter(isAnchorVisible) @@ -176,11 +178,13 @@ export const createGeoLink = (link) => { link.onmouseout = () => removeLeaderLines(link) link.onclick = (event) => { event.preventDefault() + if (link.dataset.valid === 'false') return + removeLeaderLines(link) getMarkersFromMaps(link) .forEach(updateMapCameraByMarker([ Number(link.dataset.lon), - Number(link.dataset.lat) + Number(link.dataset.lat), ])) } return true diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 6602f29..974d4a3 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs @@ -179,7 +179,7 @@ export const generateMaps = (container, { /** Set CRS and GeoLinks */ register(proj4) - fromEPSGCode(crs).then(() => { + fromEPSGCode(crs).then(projection => { const transform = proj4(crs, 'EPSG:4326').forward Array.from(container.querySelectorAll(geoLinkSelector)) @@ -200,6 +200,13 @@ export const generateMaps = (container, { params.set('q', `${lat},${lon}`) link.search = params + if (projection.getUnits() === 'degrees' && + (lon > 180 || lon < -180 || lat > 90 || lat < -90) + ) { + link.dataset.valid = false + link.title = `Invalid Coordinate, maybe try another crs other than ${crs}` + } + return link }) .forEach(utils.createGeoLink) -- cgit v1.2.3-70-g09d2