proximity
This commit is contained in:
parent
d536f42040
commit
fc7ce1e8ca
|
|
@ -27,6 +27,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
geo-wiki {
|
geo-wiki {
|
||||||
|
display: block;
|
||||||
background: white;
|
background: white;
|
||||||
border: solid 2px black;
|
border: solid 2px black;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
@ -45,6 +46,13 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-color: #b4d8f63b;
|
background-color: #b4d8f63b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
folk-weather {
|
||||||
|
display: block;
|
||||||
|
background: white;
|
||||||
|
border: solid 2px black;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -53,24 +61,30 @@
|
||||||
<folk-map coordinates="52.09, 5.12" zoom="13"></folk-map>
|
<folk-map coordinates="52.09, 5.12" zoom="13"></folk-map>
|
||||||
</fc-geometry>
|
</fc-geometry>
|
||||||
|
|
||||||
<fc-geometry id="g2" x="50" y="550" width="400" height="250">
|
<fc-geometry id="g2" x="50" y="600" width="400" height="250">
|
||||||
<folk-map coordinates="51.50404120260676, -0.14007568359375003" zoom="13"></folk-map>
|
<folk-map coordinates="51.50404120260676, -0.14007568359375003" zoom="13"></folk-map>
|
||||||
</fc-geometry>
|
</fc-geometry>
|
||||||
|
|
||||||
<fc-geometry id="g3" x="500" y="400" width="500" height="300">
|
<fc-geometry id="g3" x="500" y="500" width="500" height="300">
|
||||||
<geo-wiki coordinates="51.50404120260676, -0.14007568359375003"></geo-wiki>
|
<geo-wiki coordinates="51.50404120260676, -0.14007568359375003"></geo-wiki>
|
||||||
</fc-geometry>
|
</fc-geometry>
|
||||||
|
|
||||||
|
<fc-geometry id="g4" x="450" y="100">
|
||||||
|
<folk-weather coordinates="51.50404120260676, -0.14007568359375003"></folk-weather>
|
||||||
|
</fc-geometry>
|
||||||
</folk-proximity>
|
</folk-proximity>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
|
import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
|
||||||
import { FolkMap } from '../src/folk-map.ts';
|
import { FolkMap } from '../src/folk-map.ts';
|
||||||
import { FolkHull } from '../src/folk-hull.ts';
|
import { FolkWeather } from '../src/folk-weather.ts';
|
||||||
import { collisionDetection } from '../src/collision.ts';
|
import { FolkCluster, FolkProximity } from '../src/folk-proximity.ts';
|
||||||
|
|
||||||
FolkGeometry.register();
|
FolkGeometry.register();
|
||||||
FolkMap.register();
|
FolkMap.register();
|
||||||
FolkHull.register();
|
FolkCluster.register();
|
||||||
|
FolkProximity.register();
|
||||||
|
FolkWeather.register();
|
||||||
|
|
||||||
class GeoWiki extends HTMLElement {
|
class GeoWiki extends HTMLElement {
|
||||||
static tagName = 'geo-wiki';
|
static tagName = 'geo-wiki';
|
||||||
|
|
@ -136,136 +150,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
GeoWiki.register();
|
GeoWiki.register();
|
||||||
|
|
||||||
const PROXIMITY = 50;
|
|
||||||
|
|
||||||
class FolkCluster extends FolkHull {
|
|
||||||
static tagName = 'folk-cluster';
|
|
||||||
|
|
||||||
#data = new Map();
|
|
||||||
|
|
||||||
isElementInCluster(element) {
|
|
||||||
return this.sourceElements.includes(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
isElementInProximity(element) {
|
|
||||||
for (const el of this.sourceElements) {
|
|
||||||
if (collisionDetection(el.getClientRect(), element.getClientRect(), PROXIMITY)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
addElements(...elements) {
|
|
||||||
this.sources += elements.map((el) => `#${el.id}`).join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
removeElement(element) {
|
|
||||||
this.sources = this.sourceElements
|
|
||||||
.filter((el) => el !== element)
|
|
||||||
.map((el) => `#${el.id}`)
|
|
||||||
.join(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FolkCluster.register();
|
|
||||||
|
|
||||||
class FolkProximity extends HTMLElement {
|
|
||||||
static tagName = 'folk-proximity';
|
|
||||||
|
|
||||||
static register() {
|
|
||||||
customElements.define(this.tagName, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
#clusters = new Set();
|
|
||||||
#geometries = Array.from(this.querySelectorAll('fc-geometry'));
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.addEventListener('move', this.#handleProximity);
|
|
||||||
this.addEventListener('resize', this.#handleProximity);
|
|
||||||
// document.addEventListener('recenter', (e) => {
|
|
||||||
// proximityMap.get(e.target.parentElement)?.forEach((el) => {
|
|
||||||
// const content = el.firstElementChild;
|
|
||||||
// if (content instanceof GeoWiki) {
|
|
||||||
// const { lat, lng } = e.target.coordinates;
|
|
||||||
// content.coordinates = [lat, lng];
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
#handleProximity = (e) => {
|
|
||||||
const el = e.target;
|
|
||||||
const cluster = this.#findCluster(el);
|
|
||||||
|
|
||||||
if (cluster === null) {
|
|
||||||
for (const cluster of this.#clusters) {
|
|
||||||
// what if its in proximity to multiple clusters?
|
|
||||||
if (cluster.isElementInProximity(element)) {
|
|
||||||
cluster.addElements(element);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const geometry of this.#geometries) {
|
|
||||||
if (geometry === el) break;
|
|
||||||
|
|
||||||
if (collisionDetection(geometry.getClientRect(), el.getClientRect(), PROXIMITY)) {
|
|
||||||
const cluster = document.createElement('folk-cluster');
|
|
||||||
cluster.addElements(geometry, el);
|
|
||||||
this.#clusters.add(cluster);
|
|
||||||
this.appendChild(cluster);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const isInCluster = cluster.sourceElements
|
|
||||||
.filter((element) => el !== element)
|
|
||||||
.some((element) => collisionDetection(el.getClientRect(), element.getClientRect(), PROXIMITY));
|
|
||||||
|
|
||||||
if (!isInCluster) {
|
|
||||||
cluster.removeElement(el);
|
|
||||||
|
|
||||||
if (cluster.sourcesMap.size === 1) {
|
|
||||||
this.#clusters.delete(cluster);
|
|
||||||
cluster.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#findCluster(element) {
|
|
||||||
for (const cluster of this.#clusters) {
|
|
||||||
if (cluster.isElementInCluster(element)) return cluster;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FolkProximity.register();
|
|
||||||
|
|
||||||
/* 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 = collisionDetection(el.getClientRect(), e.target.getClientRect(), 100);
|
|
||||||
if (isNowIntersecting && !alreadyIntersection) {
|
|
||||||
console.log('create cluster');
|
|
||||||
set.add(e.target);
|
|
||||||
proximityMap.get(e.target)?.add(el);
|
|
||||||
// hull = document.createElement('folk-hull');
|
|
||||||
// hull.sources = `#${el.id}, #${e.target.id}`;
|
|
||||||
// document.body.append(hull);
|
|
||||||
} else if (alreadyIntersection && !isNowIntersecting) {
|
|
||||||
console.log('delete cluster');
|
|
||||||
set.delete(e.target);
|
|
||||||
proximityMap.get(e.target)?.delete(el);
|
|
||||||
// hull.remove();
|
|
||||||
// hull = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}); */
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import { FolkSet } from './folk-set';
|
import { FolkSet } from './folk-set';
|
||||||
import { Vertex, verticesToPolygon } from './arrows/utils';
|
import { Vertex, verticesToPolygon } from './arrows/utils';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'folk-hull': FolkHull;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class FolkHull extends FolkSet {
|
export class FolkHull extends FolkSet {
|
||||||
static tagName = 'folk-hull';
|
static tagName = 'folk-hull';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { collisionDetection } from './collision';
|
||||||
|
import { FolkHull } from './folk-hull';
|
||||||
|
import { FolkGeometry } from './canvas/fc-geometry.ts';
|
||||||
|
|
||||||
|
// TODO dont hard code this
|
||||||
|
const PROXIMITY = 50;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'folk-cluster': FolkCluster;
|
||||||
|
'folk-proximity': FolkProximity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FolkCluster extends FolkHull {
|
||||||
|
static tagName = 'folk-cluster';
|
||||||
|
|
||||||
|
#data = new Map();
|
||||||
|
|
||||||
|
isElementInCluster(element: FolkGeometry) {
|
||||||
|
return this.sourceElements.includes(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
isElementInProximity(element: FolkGeometry) {
|
||||||
|
for (const el of this.sourceElements as FolkGeometry[]) {
|
||||||
|
if (collisionDetection(el.getClientRect(), element.getClientRect(), PROXIMITY)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addElements(...elements) {
|
||||||
|
this.sources = this.sourceElements
|
||||||
|
.concat(elements)
|
||||||
|
.map((el) => `#${el.id}`)
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
removeElement(element) {
|
||||||
|
this.sources = this.sourceElements
|
||||||
|
.filter((el) => el !== element)
|
||||||
|
.map((el) => `#${el.id}`)
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FolkProximity extends HTMLElement {
|
||||||
|
static tagName = 'folk-proximity';
|
||||||
|
|
||||||
|
static register() {
|
||||||
|
customElements.define(this.tagName, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#clusters = new Set<FolkCluster>();
|
||||||
|
#geometries = Array.from(this.querySelectorAll('fc-geometry'));
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.addEventListener('move', this.#handleProximity);
|
||||||
|
this.addEventListener('resize', this.#handleProximity);
|
||||||
|
// document.addEventListener('recenter', (e) => {
|
||||||
|
// proximityMap.get(e.target.parentElement)?.forEach((el) => {
|
||||||
|
// const content = el.firstElementChild;
|
||||||
|
// if (content instanceof GeoWiki) {
|
||||||
|
// const { lat, lng } = e.target.coordinates;
|
||||||
|
// content.coordinates = [lat, lng];
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
#handleProximity = (e) => {
|
||||||
|
const el = e.target as FolkGeometry;
|
||||||
|
|
||||||
|
const cluster = this.#findCluster(el);
|
||||||
|
|
||||||
|
if (cluster === null) {
|
||||||
|
for (const cluster of this.#clusters) {
|
||||||
|
// what if its in proximity to multiple clusters?
|
||||||
|
if (cluster.isElementInProximity(el)) {
|
||||||
|
cluster.addElements(el);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const geometry of this.#geometries) {
|
||||||
|
if (geometry === el) break;
|
||||||
|
|
||||||
|
if (collisionDetection(geometry.getClientRect(), el.getClientRect(), PROXIMITY)) {
|
||||||
|
const cluster = document.createElement('folk-cluster');
|
||||||
|
cluster.addElements(geometry, el);
|
||||||
|
this.#clusters.add(cluster);
|
||||||
|
this.appendChild(cluster);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const isInCluster = (cluster.sourceElements as FolkGeometry[])
|
||||||
|
.filter((element) => el !== element)
|
||||||
|
.some((element) => collisionDetection(el.getClientRect(), element.getClientRect(), PROXIMITY));
|
||||||
|
|
||||||
|
if (!isInCluster) {
|
||||||
|
cluster.removeElement(el);
|
||||||
|
|
||||||
|
if (cluster.sourcesMap.size === 1) {
|
||||||
|
this.#clusters.delete(cluster);
|
||||||
|
cluster.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#findCluster(element) {
|
||||||
|
for (const cluster of this.#clusters) {
|
||||||
|
if (cluster.isElementInCluster(element)) return cluster;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,12 @@ import { ClientRectObserverEntry, ClientRectObserverManager } from './client-rec
|
||||||
|
|
||||||
const clientRectObserver = new ClientRectObserverManager();
|
const clientRectObserver = new ClientRectObserverManager();
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'folk-set': FolkSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class FolkSet extends HTMLElement {
|
export class FolkSet extends HTMLElement {
|
||||||
static tagName = 'folk-set';
|
static tagName = 'folk-set';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
interface Weather {
|
||||||
|
temperature: string;
|
||||||
|
windSpeed: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'folk-weather': FolkWeather;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FolkWeather extends HTMLElement {
|
||||||
|
static tagName = 'folk-weather';
|
||||||
|
|
||||||
|
static register() {
|
||||||
|
customElements.define(this.tagName, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
static observedAttributes = ['coordinates'];
|
||||||
|
|
||||||
|
#coordinates = [0, 0] as const;
|
||||||
|
#results: Weather | null = null;
|
||||||
|
|
||||||
|
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.fetchWeather(this.#coordinates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchWeather([lat, long]: readonly [number, number]) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
latitude: lat.toString(),
|
||||||
|
longitude: long.toString(),
|
||||||
|
current: 'temperature_2m,wind_speed_10m',
|
||||||
|
temperature_unit: 'fahrenheit',
|
||||||
|
wind_speed_unit: 'mph',
|
||||||
|
});
|
||||||
|
// https://www.mediawiki.org/wiki/API:Geosearch
|
||||||
|
this.#results = await fetch(`https://api.open-meteo.com/v1/forecast?${params}`)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ current, current_units }) => ({
|
||||||
|
temperature: `${current.temperature_2m} ${current_units.temperature_2m}`,
|
||||||
|
windSpeed: `${current.wind_speed_10m} ${current_units.wind_speed_10m}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.#renderResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
#renderResults() {
|
||||||
|
if (this.#results === null) {
|
||||||
|
this.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.innerHTML = `
|
||||||
|
<p>Temperature: ${this.#results.temperature}</p>
|
||||||
|
<p>Wind Speed: ${this.#results.windSpeed}</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue