maps and proximity
This commit is contained in:
parent
b0c5311d54
commit
7f01c93d7c
152
demo/maps.html
152
demo/maps.html
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Shapes - Collision</title>
|
||||
<title>Maps</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
|
|
@ -19,22 +19,168 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
leaflet-map {
|
||||
leaflet-map,
|
||||
geo-wiki {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
geo-wiki {
|
||||
ul {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
scroll-padding-block-end: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<spatial-geometry x="25" y="100" width="500" height="300">
|
||||
<spatial-geometry x="25" y="100" width="400" height="200">
|
||||
<leaflet-map coordinates="52.09, 5.12" zoom="13"></leaflet-map>
|
||||
</spatial-geometry>
|
||||
|
||||
<spatial-geometry x="50" y="550" width="400" height="250">
|
||||
<leaflet-map coordinates="51.50404120260676, -0.14007568359375003" zoom="13"></leaflet-map>
|
||||
</spatial-geometry>
|
||||
|
||||
<spatial-geometry x="500" y="400" width="500" height="300">
|
||||
<geo-wiki coordinates="51.50404120260676, -0.14007568359375003"></geo-wiki>
|
||||
</spatial-geometry>
|
||||
|
||||
<script type="module">
|
||||
import { SpatialGeometry } from '../src/elements/spatial-geometry.ts';
|
||||
import { LeafletMap } from '../src/maps';
|
||||
SpatialGeometry.register();
|
||||
LeafletMap.register();
|
||||
|
||||
function collisionDetection(rect1, rect2) {
|
||||
return (
|
||||
rect1.left < rect2.right &&
|
||||
rect1.right > rect2.left &&
|
||||
rect1.top < rect2.bottom &&
|
||||
rect1.bottom > rect2.top
|
||||
);
|
||||
}
|
||||
|
||||
function proximityDetection(rect1, rect2, distance = 30) {
|
||||
return collisionDetection(
|
||||
DOMRectReadOnly.fromRect({
|
||||
x: rect1.x - distance,
|
||||
y: rect1.y - distance,
|
||||
height: rect1.height + distance * 2,
|
||||
width: rect1.width + distance * 2,
|
||||
}),
|
||||
rect2
|
||||
);
|
||||
}
|
||||
|
||||
const proximityMap = new Map(
|
||||
Array.from(document.querySelectorAll('spatial-geometry')).map((el) => [el, new Set()])
|
||||
);
|
||||
|
||||
function handleProximity(e) {
|
||||
proximityMap.forEach((set, el) => {
|
||||
if (el !== e.target) {
|
||||
const alreadyIntersection = set.has(e.target);
|
||||
// TODO: refactor this hack once resizing and the vertices API are figured out
|
||||
const isNowIntersecting = proximityDetection(
|
||||
DOMRectReadOnly.fromRect({ x: el.x, y: el.y, height: el.height, width: el.width }),
|
||||
DOMRectReadOnly.fromRect({
|
||||
x: e.target.x,
|
||||
y: e.target.y,
|
||||
height: e.target.height,
|
||||
width: e.target.width,
|
||||
})
|
||||
);
|
||||
if (isNowIntersecting && !alreadyIntersection) {
|
||||
set.add(e.target);
|
||||
proximityMap.get(e.target)?.add(el);
|
||||
} else if (alreadyIntersection && !isNowIntersecting) {
|
||||
set.delete(e.target);
|
||||
proximityMap.get(e.target)?.delete(el);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('move', handleProximity);
|
||||
document.addEventListener('resize', handleProximity);
|
||||
document.addEventListener('recenter', (e) => {
|
||||
proximityMap.get(e.target.parentElement)?.forEach((el) => {
|
||||
const content = el.firstElementChild;
|
||||
if (content instanceof GeoWiki) {
|
||||
const { lat, lng } = e.detail;
|
||||
content.coordinates = [lat, lng];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
class GeoWiki extends HTMLElement {
|
||||
static tagName = 'geo-wiki';
|
||||
|
||||
static register() {
|
||||
customElements.define(this.tagName, this);
|
||||
}
|
||||
|
||||
static observedAttributes = ['coordinates'];
|
||||
|
||||
#coordinates = [0, 0];
|
||||
#results = [];
|
||||
|
||||
get coordinates() {
|
||||
return this.#coordinates;
|
||||
}
|
||||
|
||||
set coordinates(coordinates) {
|
||||
this.setAttribute('coordinates', coordinates.join(', '));
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (name === 'coordinates') {
|
||||
this.#coordinates = newValue.split(',').map((str) => Number(str)) || [0, 0];
|
||||
this.searchWiki(this.#coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
async searchWiki([lat, long]) {
|
||||
const params = new URLSearchParams({
|
||||
action: 'query',
|
||||
format: 'json',
|
||||
list: 'geosearch',
|
||||
gscoord: `${lat}|${long}`,
|
||||
gsradius: '1000',
|
||||
gslimit: '50',
|
||||
origin: '*',
|
||||
});
|
||||
// https://www.mediawiki.org/wiki/API:Geosearch
|
||||
this.#results = await fetch(`https://en.wikipedia.org/w/api.php?${params}`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => data?.query?.geosearch ?? []);
|
||||
|
||||
this.#renderResults();
|
||||
}
|
||||
|
||||
#renderResults() {
|
||||
this.firstElementChild?.remove();
|
||||
|
||||
const list = document.createElement('ul');
|
||||
|
||||
for (const result of this.#results) {
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
a.href = `https://en.wikipedia.org/wiki/${result.title}`;
|
||||
a.textContent = result.title;
|
||||
li.appendChild(a);
|
||||
list.appendChild(li);
|
||||
}
|
||||
|
||||
this.appendChild(list);
|
||||
}
|
||||
}
|
||||
|
||||
GeoWiki.register();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
export type Shape = 'rectangle' | 'circle' | 'triangle';
|
||||
|
||||
// Can we make adding new shapes extensible via a static property?
|
||||
const shapes = new Set(['rectangle', 'circle', 'triangle']);
|
||||
|
||||
export type MoveEventDetail = { movementX: number; movementY: number };
|
||||
|
||||
export class MoveEvent extends CustomEvent<MoveEventDetail> {
|
||||
|
|
@ -33,7 +30,7 @@ styles.replaceSync(`
|
|||
display: block;
|
||||
position: absolute;
|
||||
padding: 20px 10px 10px;
|
||||
cursor: var(--fc-grab, grab);
|
||||
cursor: var(--fc-move, move);
|
||||
content-visibility: auto;
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +49,6 @@ styles.replaceSync(`
|
|||
}
|
||||
|
||||
:host(:state(moving)) {
|
||||
cursor: var(--fc-grabbing, grabbing);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
|
@ -144,13 +140,11 @@ export class SpatialGeometry extends HTMLElement {
|
|||
customElements.define(this.tagName, this);
|
||||
}
|
||||
|
||||
static observedAttributes = ['type', 'x', 'y', 'width', 'height', 'rotate'];
|
||||
|
||||
#internals: ElementInternals;
|
||||
#internals = this.attachInternals();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#internals = this.attachInternals();
|
||||
|
||||
this.addEventListener('pointerdown', this);
|
||||
this.addEventListener('lostpointercapture', this);
|
||||
this.addEventListener('touchstart', this);
|
||||
|
|
@ -188,7 +182,8 @@ export class SpatialGeometry extends HTMLElement {
|
|||
}
|
||||
|
||||
set x(x: number) {
|
||||
this.setAttribute('x', x.toString());
|
||||
this.#x = x;
|
||||
this.#requestUpdate('x');
|
||||
}
|
||||
|
||||
#previousY = 0;
|
||||
|
|
@ -198,27 +193,30 @@ export class SpatialGeometry extends HTMLElement {
|
|||
}
|
||||
|
||||
set y(y: number) {
|
||||
this.setAttribute('y', y.toString());
|
||||
this.#y = y;
|
||||
this.#requestUpdate('y');
|
||||
}
|
||||
|
||||
#previousWidth = 0;
|
||||
#width = 0;
|
||||
#width = 1;
|
||||
get width(): number {
|
||||
return this.#width;
|
||||
}
|
||||
|
||||
set width(width: number) {
|
||||
this.setAttribute('width', width.toString());
|
||||
this.#width = width;
|
||||
this.#requestUpdate('width');
|
||||
}
|
||||
|
||||
#previousHeight = 0;
|
||||
#height = 0;
|
||||
#height = 1;
|
||||
get height(): number {
|
||||
return this.#height;
|
||||
}
|
||||
|
||||
set height(height: number) {
|
||||
this.setAttribute('height', height.toString());
|
||||
this.#height = height;
|
||||
this.#requestUpdate('height');
|
||||
}
|
||||
|
||||
#previousRotate = 0;
|
||||
|
|
@ -228,36 +226,17 @@ export class SpatialGeometry extends HTMLElement {
|
|||
}
|
||||
|
||||
set rotate(rotate: number) {
|
||||
this.setAttribute('rotate', rotate.toString());
|
||||
this.#rotate = rotate;
|
||||
this.#requestUpdate('rotate');
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
|
||||
if (name === 'x') {
|
||||
this.#previousX = this.#x;
|
||||
this.#x = Number(newValue);
|
||||
this.#requestUpdate('x');
|
||||
} else if (name === 'y') {
|
||||
this.#previousY = this.#y;
|
||||
this.#y = Number(newValue);
|
||||
this.#requestUpdate('y');
|
||||
} else if (name === 'width') {
|
||||
this.#previousWidth = this.#width;
|
||||
this.#width = Number(newValue);
|
||||
this.#requestUpdate('width');
|
||||
} else if (name === 'height') {
|
||||
this.#previousHeight = this.#height;
|
||||
this.#height = Number(newValue);
|
||||
this.#requestUpdate('height');
|
||||
} else if (name === 'rotate') {
|
||||
this.#previousRotate = this.#rotate;
|
||||
this.#rotate = Number(newValue);
|
||||
this.#requestUpdate('rotate');
|
||||
} else if (name === 'type') {
|
||||
if (shapes.has(newValue)) {
|
||||
this.#type = newValue as Shape;
|
||||
this.#requestUpdate('type');
|
||||
}
|
||||
}
|
||||
connectedCallback() {
|
||||
this.type = (this.getAttribute('type') || 'rectangle') as Shape;
|
||||
this.x = Number(this.getAttribute('x')) || 0;
|
||||
this.y = Number(this.getAttribute('y')) || 0;
|
||||
this.height = Number(this.getAttribute('height')) || 0;
|
||||
this.width = Number(this.getAttribute('width')) || 0;
|
||||
this.rotate = Number(this.getAttribute('rotate')) || 0;
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { map, Map, tileLayer } from 'leaflet';
|
||||
import { LatLng, LatLngExpression, LeafletEvent, map, Map, tileLayer } from 'leaflet';
|
||||
|
||||
// @ts-ignore
|
||||
// Vite specific import :(
|
||||
|
|
@ -15,6 +15,12 @@ styles.replaceSync(`${css}
|
|||
}
|
||||
`);
|
||||
|
||||
export class RecenterEvent extends CustomEvent<LatLng> {
|
||||
constructor(detail: LatLng) {
|
||||
super('recenter', { detail, bubbles: true });
|
||||
}
|
||||
}
|
||||
|
||||
export class LeafletMap extends HTMLElement {
|
||||
static tagName = 'leaflet-map';
|
||||
|
||||
|
|
@ -27,7 +33,7 @@ export class LeafletMap extends HTMLElement {
|
|||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.handleEvent = this.handleEvent.bind(this);
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
shadow.adoptedStyleSheets.push(styles);
|
||||
shadow.appendChild(this.#container);
|
||||
|
|
@ -41,6 +47,23 @@ export class LeafletMap extends HTMLElement {
|
|||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
})
|
||||
);
|
||||
this.#map.setView([52.09, 5.12], 13);
|
||||
const coordinates = (this.getAttribute('coordinates')
|
||||
?.split(',')
|
||||
.map((str) => Number(str)) || [0, 0]) as LatLngExpression;
|
||||
const zoom = Number(this.getAttribute('zoom') || 13);
|
||||
this.#map.setView(coordinates, zoom);
|
||||
|
||||
this.#map.on('zoom', this.handleEvent);
|
||||
this.#map.on('moveend', this.handleEvent);
|
||||
}
|
||||
|
||||
handleEvent(event: LeafletEvent) {
|
||||
switch (event.type) {
|
||||
case 'zoom':
|
||||
case 'moveend': {
|
||||
this.dispatchEvent(new RecenterEvent(this.#map.getCenter()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue