diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/css/dumbymap.css | 6 | ||||
-rw-r--r-- | src/dumbyUtils.mjs | 39 | ||||
-rw-r--r-- | src/dumbymap.mjs | 59 | ||||
-rw-r--r-- | src/editor.mjs | 13 |
4 files changed, 94 insertions, 23 deletions
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, | |||
152 | background-color: #9ee7ea; | 152 | background-color: #9ee7ea; |
153 | } | 153 | } |
154 | 154 | ||
155 | &[data-valid="false"] { | ||
156 | background-color: lightgray; | ||
157 | |||
158 | opacity: 0.7; | ||
159 | } | ||
160 | |||
155 | &:hover, &.drag { | 161 | &:hover, &.drag { |
156 | background-image: none; | 162 | background-image: none; |
157 | 163 | ||
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs index 170cb16..2f92c18 100644 --- a/src/dumbyUtils.mjs +++ b/src/dumbyUtils.mjs | |||
@@ -1,6 +1,8 @@ | |||
1 | import LeaderLine from 'leader-line' | 1 | import LeaderLine from 'leader-line' |
2 | import { insideWindow, insideParent } from './utils' | 2 | import { insideWindow, insideParent } from './utils' |
3 | 3 | ||
4 | export const coordPattern = /^geo:([-]?[0-9.]+),([-]?[0-9.]+)/ | ||
5 | |||
4 | /** | 6 | /** |
5 | * focusNextMap. | 7 | * focusNextMap. |
6 | * | 8 | * |
@@ -100,11 +102,12 @@ const getMarkersFromMaps = link => { | |||
100 | .filter(map => link.targets ? link.targets.includes(map.id) : true) | 102 | .filter(map => link.targets ? link.targets.includes(map.id) : true) |
101 | .map(map => { | 103 | .map(map => { |
102 | const renderer = map.renderer | 104 | const renderer = map.renderer |
103 | const markerTitle = `${link.targets ?? 'all'}@${link.xy}` | 105 | const markerTitle = `${link.targets ?? 'all'}@${link.dataset.xy}` |
106 | const lonLat = [Number(link.dataset.lon), Number(link.dataset.lat)] | ||
104 | 107 | ||
105 | return map.querySelector(`.marker[title="${markerTitle}"]`) ?? | 108 | return map.querySelector(`.marker[title="${markerTitle}"]`) ?? |
106 | renderer.addMarker({ | 109 | renderer.addMarker({ |
107 | xy: link.xy, | 110 | xy: lonLat, |
108 | title: markerTitle, | 111 | title: markerTitle, |
109 | type: link.type, | 112 | type: link.type, |
110 | }) | 113 | }) |
@@ -138,26 +141,32 @@ const addLeaderLine = (link, target) => { | |||
138 | */ | 141 | */ |
139 | export const createGeoLink = (link) => { | 142 | export const createGeoLink = (link) => { |
140 | const url = new URL(link.href) | 143 | const url = new URL(link.href) |
141 | const xyInParams = url.searchParams.get('xy')?.split(',')?.map(Number) | 144 | const params = new URLSearchParams(link.search) |
142 | const xy = xyInParams ?? url?.href | 145 | const xyInParams = params.get('xy')?.split(',')?.map(Number) |
143 | ?.match(/^geo:([-]?[0-9.]+),([-]?[0-9.]+)/) | 146 | const [lon, lat] = url.href |
147 | ?.match(coordPattern) | ||
144 | ?.splice(1) | 148 | ?.splice(1) |
145 | ?.reverse() | 149 | ?.reverse() |
146 | ?.map(Number) | 150 | ?.map(Number) |
151 | const xy = xyInParams ?? [lon, lat] | ||
147 | 152 | ||
148 | if (!xy || isNaN(xy[0]) || isNaN(xy[1])) return false | 153 | if (!xy || isNaN(xy[0]) || isNaN(xy[1])) return false |
149 | 154 | ||
150 | // Geo information in link | 155 | // Geo information in link |
151 | link.url = url | 156 | link.dataset.xy = xy |
152 | link.xy = xy | 157 | link.dataset.lon = lon |
158 | link.dataset.lat = lat | ||
153 | link.classList.add('with-leader-line', 'geolink') | 159 | link.classList.add('with-leader-line', 'geolink') |
154 | link.targets = link.url.searchParams.get('id')?.split(',') ?? null | 160 | // TODO refactor as data attribute |
155 | link.type = link.url.searchParams.get('type') ?? null | 161 | link.targets = params.get('id')?.split(',') ?? null |
162 | link.type = params.get('type') ?? null | ||
156 | 163 | ||
157 | link.lines = [] | 164 | link.lines = [] |
158 | 165 | ||
159 | // LeaderLine | 166 | // LeaderLine |
160 | link.onmouseover = () => { | 167 | link.onmouseover = () => { |
168 | if (link.dataset.valid === 'false') return | ||
169 | |||
161 | const anchors = getMarkersFromMaps(link) | 170 | const anchors = getMarkersFromMaps(link) |
162 | anchors | 171 | anchors |
163 | .filter(isAnchorVisible) | 172 | .filter(isAnchorVisible) |
@@ -169,8 +178,14 @@ export const createGeoLink = (link) => { | |||
169 | link.onmouseout = () => removeLeaderLines(link) | 178 | link.onmouseout = () => removeLeaderLines(link) |
170 | link.onclick = (event) => { | 179 | link.onclick = (event) => { |
171 | event.preventDefault() | 180 | event.preventDefault() |
181 | if (link.dataset.valid === 'false') return | ||
182 | |||
172 | removeLeaderLines(link) | 183 | removeLeaderLines(link) |
173 | getMarkersFromMaps(link).forEach(updateMapCameraByMarker(link.xy)) | 184 | getMarkersFromMaps(link) |
185 | .forEach(updateMapCameraByMarker([ | ||
186 | Number(link.dataset.lon), | ||
187 | Number(link.dataset.lat), | ||
188 | ])) | ||
174 | } | 189 | } |
175 | return true | 190 | return true |
176 | } | 191 | } |
@@ -229,9 +244,9 @@ const removeLeaderLines = link => { | |||
229 | * @param {Number[]} xy | 244 | * @param {Number[]} xy |
230 | * @return {Function} function | 245 | * @return {Function} function |
231 | */ | 246 | */ |
232 | const updateMapCameraByMarker = xy => marker => { | 247 | const updateMapCameraByMarker = lonLat => marker => { |
233 | const renderer = marker.closest('.mapclay')?.renderer | 248 | const renderer = marker.closest('.mapclay')?.renderer |
234 | renderer.updateCamera({ center: xy }, true) | 249 | renderer.updateCamera({ center: lonLat }, true) |
235 | } | 250 | } |
236 | 251 | ||
237 | /** | 252 | /** |
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 35a4614..974d4a3 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs | |||
@@ -9,6 +9,8 @@ import { Layout, SideBySide, Overlay } from './Layout' | |||
9 | import * as utils from './dumbyUtils' | 9 | import * as utils from './dumbyUtils' |
10 | import * as menuItem from './MenuItem' | 10 | import * as menuItem from './MenuItem' |
11 | import PlainModal from 'plain-modal' | 11 | import PlainModal from 'plain-modal' |
12 | import proj4 from 'proj4' | ||
13 | import { register, fromEPSGCode } from 'ol/proj/proj4' | ||
12 | 14 | ||
13 | /** Selector of special HTML Elements */ | 15 | /** Selector of special HTML Elements */ |
14 | const mapBlockSelector = 'pre:has(.language-map)' | 16 | const mapBlockSelector = 'pre:has(.language-map)' |
@@ -59,8 +61,8 @@ export const markdown2HTML = (container, mdContent) => { | |||
59 | validate: coordinateRegex, | 61 | validate: coordinateRegex, |
60 | normalize: function (match) { | 62 | normalize: function (match) { |
61 | const [, , x, sep, y] = match.text.match(coordinateRegex) | 63 | const [, , x, sep, y] = match.text.match(coordinateRegex) |
62 | match.url = `geo:${y},${x}?xy=${x},${y}` | 64 | match.url = `geo:${y},${x}` |
63 | match.text = `${x}${sep} ${y}` | 65 | match.text = `${x}${sep}${y}` |
64 | match.index += match.text.indexOf(x) + 1 | 66 | match.index += match.text.indexOf(x) + 1 |
65 | return match | 67 | return match |
66 | }, | 68 | }, |
@@ -111,11 +113,17 @@ export const markdown2HTML = (container, mdContent) => { | |||
111 | * | 113 | * |
112 | * @param {HTMLElement} container - The container element for the maps | 114 | * @param {HTMLElement} container - The container element for the maps |
113 | * @param {Object} options - Configuration options | 115 | * @param {Object} options - Configuration options |
116 | * @param {string} options.crs - CRS in EPSG/ESRI code, see epsg.io | ||
114 | * @param {number} [options.delay=1000] - Delay before rendering maps (in milliseconds) | 117 | * @param {number} [options.delay=1000] - Delay before rendering maps (in milliseconds) |
115 | * @param {Function} options.mapCallback - Callback function to be called after map rendering | 118 | * @param {Function} options.mapCallback - Callback function to be called after map rendering |
116 | */ | 119 | */ |
117 | export const generateMaps = (container, { layouts = [], delay, renderCallback } = {}) => { | 120 | export const generateMaps = (container, { |
118 | /** Prepare Contaner/HTML Holder/Showcase */ | 121 | crs = 'EPSG:4326', |
122 | layouts = [], | ||
123 | delay, | ||
124 | renderCallback, | ||
125 | } = {}) => { | ||
126 | /** Prepare Contaner/HTML-Holder/Showcase */ | ||
119 | container.classList.add('Dumby') | 127 | container.classList.add('Dumby') |
120 | delete container.dataset.layout | 128 | delete container.dataset.layout |
121 | container.dataset.layout = defaultLayouts[0].name | 129 | container.dataset.layout = defaultLayouts[0].name |
@@ -159,15 +167,50 @@ export const generateMaps = (container, { layouts = [], delay, renderCallback } | |||
159 | switchToNextLayout: throttle(utils.switchToNextLayout, 300), | 167 | switchToNextLayout: throttle(utils.switchToNextLayout, 300), |
160 | }, | 168 | }, |
161 | } | 169 | } |
162 | Object.entries(dumbymap.utils).forEach(([util, func]) => { | 170 | Object.entries(dumbymap.utils).forEach(([util, value]) => { |
163 | dumbymap.utils[util] = func.bind(dumbymap) | 171 | if (typeof value === 'function') { |
172 | dumbymap.utils[util] = value.bind(dumbymap) | ||
173 | } | ||
164 | }) | 174 | }) |
165 | 175 | ||
166 | /** Create GeoLinks and DocLinks */ | 176 | /** Create GeoLinks and DocLinks */ |
167 | container.querySelectorAll(docLinkSelector) | 177 | container.querySelectorAll(docLinkSelector) |
168 | .forEach(utils.createDocLink) | 178 | .forEach(utils.createDocLink) |
169 | container.querySelectorAll(geoLinkSelector) | 179 | |
170 | .forEach(utils.createGeoLink) | 180 | /** Set CRS and GeoLinks */ |
181 | register(proj4) | ||
182 | fromEPSGCode(crs).then(projection => { | ||
183 | const transform = proj4(crs, 'EPSG:4326').forward | ||
184 | |||
185 | Array.from(container.querySelectorAll(geoLinkSelector)) | ||
186 | .map(link => { | ||
187 | // set coordinate as lat/lon in WGS84 | ||
188 | const params = new URLSearchParams(link.search) | ||
189 | const [y, x] = link.href | ||
190 | .match(utils.coordPattern) | ||
191 | .splice(1) | ||
192 | .map(Number) | ||
193 | const [lon, lat] = transform([x, y]) | ||
194 | .map(value => value.toFixed(6)) | ||
195 | link.href = `geo:${lat},${lon}` | ||
196 | |||
197 | // set query strings | ||
198 | params.set('xy', `${x},${y}`) | ||
199 | params.set('crs', crs) | ||
200 | params.set('q', `${lat},${lon}`) | ||
201 | link.search = params | ||
202 | |||
203 | if (projection.getUnits() === 'degrees' && | ||
204 | (lon > 180 || lon < -180 || lat > 90 || lat < -90) | ||
205 | ) { | ||
206 | link.dataset.valid = false | ||
207 | link.title = `Invalid Coordinate, maybe try another crs other than ${crs}` | ||
208 | } | ||
209 | |||
210 | return link | ||
211 | }) | ||
212 | .forEach(utils.createGeoLink) | ||
213 | }) | ||
171 | 214 | ||
172 | /** | 215 | /** |
173 | * mapFocusObserver. observe for map focus | 216 | * mapFocusObserver. observe for map focus |
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' | |||
14 | */ | 14 | */ |
15 | 15 | ||
16 | // Set up Containers {{{ | 16 | // Set up Containers {{{ |
17 | /** Variables about dumbymap and editor **/ | 17 | |
18 | /** Variables: page */ | ||
18 | const url = new URL(window.location) | 19 | const url = new URL(window.location) |
20 | const pageParams = url.searchParams | ||
21 | const crs = pageParams.get('crs') ?? 'EPSG:4326' | ||
22 | |||
23 | /** Variables: dumbymap and editor **/ | ||
19 | const context = document.querySelector('[data-mode]') | 24 | const context = document.querySelector('[data-mode]') |
20 | const dumbyContainer = document.querySelector('.DumbyMap') | 25 | const dumbyContainer = document.querySelector('.DumbyMap') |
21 | dumbyContainer.dataset.scrollLine = '' | 26 | dumbyContainer.dataset.scrollLine = '' |
22 | const textArea = document.querySelector('.editor textarea') | 27 | const textArea = document.querySelector('.editor textarea') |
23 | let dumbymap | 28 | let dumbymap |
24 | 29 | ||
25 | /** Variables about Reference Style Links in Markdown */ | 30 | /** Variables: Reference Style Links in Markdown */ |
26 | const refLinkPattern = /\[([^\x5B\x5D]+)\]:\s+(\S+)(\s["'](\S+)["'])?/ | 31 | const refLinkPattern = /\[([^\x5B\x5D]+)\]:\s+(\S+)(\s["'](\S+)["'])?/ |
27 | let refLinks = [] | 32 | let refLinks = [] |
28 | 33 | ||
@@ -516,7 +521,9 @@ const menuForEditor = (event, menu) => { | |||
516 | const updateDumbyMap = (callback = null) => { | 521 | const updateDumbyMap = (callback = null) => { |
517 | markdown2HTML(dumbyContainer, editor.value()) | 522 | markdown2HTML(dumbyContainer, editor.value()) |
518 | // debounceForMap(dumbyContainer, afterMapRendered) | 523 | // debounceForMap(dumbyContainer, afterMapRendered) |
519 | dumbymap = generateMaps(dumbyContainer) | 524 | dumbymap = generateMaps(dumbyContainer, { |
525 | crs, | ||
526 | }) | ||
520 | // Set onscroll callback | 527 | // Set onscroll callback |
521 | const htmlHolder = dumbymap.htmlHolder | 528 | const htmlHolder = dumbymap.htmlHolder |
522 | htmlHolder.onscroll = updateScrollLine(htmlHolder) | 529 | htmlHolder.onscroll = updateScrollLine(htmlHolder) |