maparea 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.biomeincludes +4 -0
- package/README.md +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +198 -0
- package/dist/index.mjs +1 -0
- package/index.ts +28 -0
- package/package.json +14 -0
- package/tsconfig.json +13 -0
package/.biomeincludes
ADDED
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# maparea
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const{PI:e}=Math,t=e/180,n=180/e,r={sphM:0,WGS84:.0818191908426},i=-85.05,a=85.05,o=-180,s=180,{PI:c,sin:l,tan:u,atan:d,pow:f,exp:p,log:m,abs:h}=Math;var g=class{_p;_c;_cc;_r=new Set;_t=null;constructor(e){this._p=e,this.container.style=`position: relative; overflow: hidden;`,this.container.dataset.type=`maparea`}render(){for(let e of this._r)e(this)}onRender(e,t=!1){return this._r.add(e),t||e(this),()=>{this._r.delete(e)}}getOptions(){return{...this._p,center:this.center,bounds:this.bounds}}setOptions(e){this._p={...this._p,...e},delete this._c,delete this._cc,this.render()}get container(){if(!this._c){let e=document.querySelector(this._p.container);if(!e)throw Error(`missing container element`);this._c=e}return this._c}get box(){let e=this.container.getBoundingClientRect();return{x:e.left,y:e.top,w:e.width,h:e.height}}get bounds(){let e=this._p.bounds;return e?{...e}:{}}set bounds(e){this._p.bounds=e}get center(){let e=this._p.center;return e?e.slice():[0,0]}set center(e){this._p.center=e,delete this._cc,this.render()}get centerCoords(){if(!this._cc){let[e,t]=this.center;this._cc=this.toPixelCoords(e,t)}return this._cc}get zoom(){return this._p.zoom??15}set zoom(e){this._p.zoom=e,this._t&&=(clearTimeout(this._t),null),this._t=setTimeout(()=>{this._t=null,delete this._cc,this.render()},150)}get projection(){return this._p.projection??`WGS84`}set projection(e){this._p.projection=e,this.render()}get lang(){return this._p.lang??``}set lang(e){this._p.lang=e,this.render()}inPixelBounds(e,t){let[n,r]=this.toGeoCoords(e,t);return this.inBounds(n,r)}inBounds(e,t){let{minLat:n=i,maxLat:r=a,minLon:s=o,maxLon:c=180}=this.bounds;return e>=n&&e<=r&&t>=s&&t<=c}canMoveTo(e,t){if(!this.inBounds(e,t))return!1;let{w:n,h:r}=this.box;if(n===0||r===0)return!1;let[i,a]=this.toPixelCoords(e,t);return this.inPixelBounds(i-n/2,a-r/2)&&this.inPixelBounds(i+n/2,a+r/2)}toPixelCoords(e,n){let i=r[this.projection],a=f(2,this.zoom+7)/c,o=e*t,s=i===0?1:f((1-i*l(o))/(1+i*l(o)),i/2);return[a*(c+n*t),a*(c-m(s*u(c/4+o/2)))]}toGeoCoords(e,t){let o=r[this.projection],s=f(2,this.zoom+7)/c,l=(e/s-c)*n,u;if(o===0)u=2*(d(p(c-t/s))-c/4)*n;else{u=i;let e=a-i,n=NaN,r=0;for(;(Number.isNaN(n)||h(n-t)>.1)&&r++<30;)e/=2,n=this.toPixelCoords(u+e,l)[1],n>t&&(u+=e)}return(l<-180||l>=180)&&(l=(l+180)%360-180),(u<-90||u>=90)&&(u=(u+90)%180-90),[u,l]}};function _(e,t){return t===void 0||!(e instanceof HTMLElement)?!1:typeof t==`function`&&t(e)||typeof t==`string`&&e.closest(t)!==null}function v(e,t,n){let r=Date.now(),i=()=>{r=Date.now()},a=i=>{if(_(i.target,n)||Date.now()-r>150)return;let{box:a,centerCoords:[o,s]}=e,c=i.clientX-a.x,l=i.clientY-a.y,[u,d]=e.toGeoCoords(c-.5*a.w+o,l-.5*a.h+s);t({x:c,y:l,lat:u,lon:d,originalEvent:i})};return e.container.addEventListener(`mousedown`,i),e.container.addEventListener(`mouseup`,a),()=>{e.container.removeEventListener(`mousedown`,i),e.container.removeEventListener(`mouseup`,a)}}function y(e,{onStart:t,onMove:n,onEnd:r,wheel:i}={}){let a=null,o=null,s=Date.now(),c=!1,l=null,u=null;function d(e,t,r=100){u!==null&&(clearTimeout(u),u=null),l!==null&&(cancelAnimationFrame(l),l=null);let i=Date.now();i-s<r||(a!==null&&(a-=e),o!==null&&(o-=t),s=i,l=requestAnimationFrame(()=>{l=null,(e!==0||t!==0)&&n?.(e,t)}))}function f(e,t,n){a!==null&&o!==null&&d(a-e,o-t,n)}function p(n,r){c=!0,t?.(),n!==void 0&&(a=n),r!==void 0&&(o=r),n!==void 0&&r!==void 0&&(e.dataset.dragged=`true`),s=Date.now()}function m(t,n){c=!1,u=null,t!==void 0&&n!==void 0&&f(t,n),delete e.dataset.dragged,a=null,o=null,requestAnimationFrame(()=>{r?.()})}e.dataset.draggable=`true`;let h=null,g=null;e.addEventListener(`mousedown`,t=>{t.preventDefault(),!h&&(p(t.clientX,t.clientY),h=e=>{e.preventDefault(),f(e.clientX,e.clientY)},e.addEventListener(`mousemove`,h))}),e.addEventListener(`mouseup`,t=>{t.preventDefault(),h&&=(m(t.clientX,t.clientY),e.removeEventListener(`mousemove`,h),null)}),e.addEventListener(`touchstart`,t=>{t.preventDefault(),!g&&(p(t.touches[0]?.pageX,t.touches[0]?.pageY),g=e=>{e.preventDefault(),f(e.touches[0]?.pageX,e.touches[0]?.pageY)},e.addEventListener(`touchmove`,g))}),e.addEventListener(`touchend`,t=>{t.preventDefault(),g&&=(m(t.touches[0]?.pageX,t.touches[0]?.pageY),e.removeEventListener(`touchmove`,g),null)}),e.addEventListener(`touchcancel`,t=>{t.preventDefault(),g&&=(m(t.touches[0]?.pageX,t.touches[0]?.pageY),e.removeEventListener(`touchmove`,g),null)}),i&&e.addEventListener(`wheel`,e=>{e.preventDefault(),c||p(),e.shiftKey?d(e.deltaY,e.deltaX,10):d(e.deltaX,e.deltaY,10),u=setTimeout(()=>{m()},200)})}function b(e){let t=0,n=0;y(e.container,{onStart(){[t,n]=e.centerCoords},onMove(r,i){let a=t+r,o=n+i,s=e.toGeoCoords(a,o);e.canMoveTo(...s)&&(e.center=s,t=a,n=o)},wheel:!0})}function x(e,{storageKey:t,session:n=!1}={}){let r=n?window.sessionStorage:window.localStorage,i=t??[`maparea`,e.container.id].filter(Boolean).join(`#`),a=e.getOptions(),o=!1,s=()=>{try{r.setItem(i,JSON.stringify(e.getOptions()))}catch{}},c=()=>{try{let t=r.getItem(i);t===null?s():e.setOptions(JSON.parse(t))}catch{}},l=e=>{o=e===void 0?!o:e},u=()=>{e.setOptions(a)};c();let d=null;return e.onRender(()=>{o||(d&&=(clearTimeout(d),null),d=setTimeout(()=>{d=null,s()},350))}),{sync:c,write:s,toggle:l,reset:u}}function S(e,t){let n=new ResizeObserver(n=>{for(let r of n)if(r.contentBoxSize){if(t)if(r.contentBoxSize){let{inlineSize:e,blockSize:n}=r.contentBoxSize[0]??r.contentBoxSize;t({inlineSize:e,blockSize:n})}else{let{width:e,height:n}=r.contentRect;t({width:e,height:n})}e.render()}});return n.observe(e.container),()=>{n.unobserve(e.container)}}function C(){return Math.random().toString(36).slice(2)}function w(e,{id:t,className:n,inset:r=`0`}={}){let i=`${n?`.${n}`:``}${t?`#${t}`:``}`,a=document.querySelector(i);return a||(a=document.createElement(`div`),a.className=`${n?`${n} `:``}layer`,a.id=t??`layer-${C()}`,a.style=`position: absolute; inset: ${r};`,e.container.append(a)),a}function T(e,t){return e.toFixed(t).replace(/\.(\d*[^0])0+$/,`.$1`).replace(/\.0+$/,``)}const E=`http://www.w3.org/2000/svg`,D={className:`shape`};function O(e,t,n){let r=w(e,n??D);r.toggleAttribute(`hidden`,t.length===0);let i=r.querySelector(`svg`);i||(i=document.createElementNS(E,`svg`),i.setAttribute(`xmlns`,E),r.append(i));let a=A(i,t.map(({id:t,coords:[n,r]})=>({id:t,coords:e.toPixelCoords(n,r)})),n);if(a){let{xMin:t,xMax:n,yMin:r,yMax:o}=a,{centerCoords:[s,c],box:l}=e,u=T(n-t,3),d=T(o-r,3),f=T(.5*l.w+t-s,2),p=T(.5*l.h+r-c,2);i.setAttribute(`viewBox`,`0 0 ${u} ${d}`),i.setAttribute(`width`,u),i.setAttribute(`height`,d),i.setAttribute(`style`,`position: absolute; left: ${f}px; top: ${p}px;`)}return r}function k(e){return parseFloat(window.getComputedStyle(e).strokeWidth||e.getAttribute(`stroke-width`)||`1`)}function A(e,t,n){if(t.length===0){e.innerHTML=``;return}let r=e.querySelector(`path`);r||(r=document.createElementNS(E,`path`),e.append(r));let i=t[0].coords[0],a=i,o=t[0].coords[1],s=o,c=``;for(let{coords:[e,n]}of t)i>e&&(i=e),a<e&&(a=e),o>n&&(o=n),s<n&&(s=n);i-=10,a+=10,o-=10,s+=10;let l=Array.from(e.querySelectorAll(`.marker`)),u=0;for(let{id:a,coords:[s,d]}of t){let t=T(s-i,3),f=T(d-o,3);if(c+=`${c?` L`:`M`} ${t} ${f}`,n?.markers){let n=l[u++];n||(n=document.createElementNS(E,`circle`),n.setAttribute(`class`,`marker`),n.setAttribute(`r`,String(1.5*k(r))),e.append(n)),n.setAttribute(`cx`,t),n.setAttribute(`cy`,f),n.dataset.id=a}}for(let e=u;e<l.length;e++)l[e].remove();return r.setAttribute(`d`,c),{xMin:i,xMax:a,yMin:o,yMax:s}}function j(e,t,n){let r={id:`shape-${C()}`,className:`shape`},i=t.map(e=>({id:C(),coords:e}));e.onRender(()=>{O(e,i,{...r,...n})})}function M(e,t){let n=[],r=null,i={id:`shape-editor-${C()}`,className:`shape-editor`,markers:!0},a=()=>{O(e,n,i),t?.onUpdate?.(n)};v(e,({lat:e,lon:t,originalEvent:i})=>{let o=(i.target?.closest(`.marker`))?.dataset.id;if(o&&r){clearTimeout(r),r=null,n=n.filter(({id:e})=>e!==o),a();return}let s=()=>{n.push({id:C(),coords:[e,t]}),a(),r=null};o?r=setTimeout(s,250):s()},t?.ignoreClicks),e.onRender(a)}function N(e,t){return typeof t==`function`?t(e):t}const{floor:P,ceil:F}=Math,I=256;function L(e,t,n){return`${t},${n},${e.zoom},${e.lang}`}function R(e,t,n,{size:r=256,url:i,retries:a=0,error:o}){let s=new Image,c=0,l=(r,a)=>i?typeof i==`function`?i(e,t,n):i.replaceAll(`{x}`,String(r)).replaceAll(`{y}`,String(a)).replaceAll(`{z}`,String(e.zoom)).replaceAll(`{lang}`,e.lang):``;return s.width=r,s.height=r,s.src=l(t,n),s.dataset.id=L(e,t,n),s.style=`position: absolute;`,s.onerror=()=>{c++;let{src:i}=s;if(!i||c>a){let t=N(e,o);t&&(s.onerror=()=>{},s.src=t);return}let u=t*r,d=n*r,f=e.toPixelCoords(...e.toGeoCoords(u,d))[0],p=P(f/r);if(t!==p)s.src=l(p,n);else{let e=new URL(i);e.searchParams.set(`_t`,String(Date.now())),s.src=e.href}},s}function z(e,t,{attribution:n,attributionInset:r=`auto 0 0 auto`}){let i=t.querySelector(`.attribution`),a=N(e,n);return a?(i||(i=document.createElement(`div`),i.className=`attribution`,i.style=`position: absolute; inset: ${r};`,t.append(i)),i.innerHTML!==a&&(i.innerHTML=a),i):(i&&i.remove(),null)}function B(e){return e.querySelectorAll(`img[data-id]`)}function V(e,t){return e.querySelector(`img[data-id="${t}"]`)}function H(e,t={}){let{id:n=`tiles-${C()}`,className:r=`tiles`}=t,i=w(e,{id:n,className:r});return e.onRender(()=>{let n=z(e,i,t),{box:{w:r,h:a},centerCoords:[o,s]}=e,{size:c=256,margin:l=0}=t,u=Array.isArray(l)?l[0]:l,d=Array.isArray(l)?l[1]:l,f=F((r+2*u)/c),p=F((a+2*d)/c),m=P(o/c),h=P(s/c),g=null,_=new Set,v=``;for(let l=0;l<=f;l++){let u=m+(l%2==0?-1:1)*P(l/2);for(let l=0;l<=p;l++){let d=h+(l%2==0?-1:1)*P(l/2);v=L(e,u,d),g=V(i,v),g||(g=R(e,u,d,t),i.insertBefore(g,n));let f=.5*r+u*c-o,p=.5*a+d*c-s;g.style.left=`${T(f,2)}px`,g.style.top=`${T(p,2)}px`,_.add(v)}}for(let e of B(i)){let{id:t}=e.dataset;t&&!_.has(t)&&e.remove()}}),i}function U(e,t={}){let n=w(e,{className:`zoom-controls`,inset:`0 0 auto auto`}),{min:r,max:i,plus:a=`➕`,minus:o=`➖`}=t,s=document.createElement(`button`);s.dataset.id=`plus`,s.innerHTML=a;let c=document.createElement(`button`);c.dataset.id=`minus`,c.innerHTML=o;let l=()=>{i!==void 0&&s.toggleAttribute(`disabled`,e.zoom+1>i),r!==void 0&&c.toggleAttribute(`disabled`,e.zoom-1<r)};return n.addEventListener(`click`,t=>{let n=t.target?.closest(`button`);n&&(t.stopPropagation(),e.zoom+=n.dataset.id===`minus`?-1:1,l())}),e.onRender(l),n.append(s,c),n}function W(e){if(e.length===0)return{};let t=e[0][0],n=t,r=e[0][1],i=r;for(let[a,o]of e)t>a&&(t=a),n<a&&(n=a),r>o&&(r=o),i<o&&(i=o);return{minLat:t,maxLat:n,minLon:r,maxLon:i}}function G(e){let{minLat:t,maxLat:n,minLon:r,maxLon:i}=W(e);return[t===void 0||n===void 0?0:(n+t)/2,r===void 0||i===void 0?0:(i+r)/2]}function K(e){return Array.isArray(e)&&e.length===2&&typeof e[0]==`number`&&typeof e[1]==`number`}const q=[.005,.018];function J(e){return Array.isArray(e)?K(e)?{minLat:e[0],maxLat:e[0],minLon:e[1],maxLon:e[1]}:W(e):e}function Y(e,[t,n]=q){let{minLat:r=0,maxLat:i=0,minLon:a=0,maxLon:o=0}=J(e);return{minLat:r-t,maxLat:i+t,minLon:a-n,maxLon:o+n}}exports.DEG=n,exports.MAX_LAT=a,exports.MAX_LON=180,exports.MIN_LAT=i,exports.MIN_LON=o,exports.MapArea=g,exports.RAD=t,exports.addClickListener=v,exports.addNavigation=b,exports.addPersistence=x,exports.addResizeObserver=S,exports.addShape=j,exports.addShapeEditor=M,exports.addTiles=H,exports.addZoomControls=U,exports.eccentricityMap=r,exports.getBounds=W,exports.getCenter=G,exports.getId=C,exports.getLayer=w,exports.getVicinity=Y,exports.isGeoCoords=K,exports.resolveString=N,exports.toPrecision=T;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
type Projection = "sphM" | "WGS84";
|
|
2
|
+
|
|
3
|
+
declare const RAD: number;
|
|
4
|
+
declare const DEG: number;
|
|
5
|
+
declare const eccentricityMap: Record<Projection, number>;
|
|
6
|
+
declare const MIN_LAT = -85.05;
|
|
7
|
+
declare const MAX_LAT = 85.05;
|
|
8
|
+
declare const MIN_LON = -180;
|
|
9
|
+
declare const MAX_LON = 180;
|
|
10
|
+
|
|
11
|
+
type BoxDimensions = {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
w: number;
|
|
15
|
+
h: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type GeoBounds = {
|
|
19
|
+
minLat?: number | undefined;
|
|
20
|
+
maxLat?: number | undefined;
|
|
21
|
+
minLon?: number | undefined;
|
|
22
|
+
maxLon?: number | undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type GeoCoords = [number, number];
|
|
26
|
+
|
|
27
|
+
type PixelCoords = [number, number];
|
|
28
|
+
|
|
29
|
+
type MapAreaOptions = {
|
|
30
|
+
container: string;
|
|
31
|
+
center?: GeoCoords;
|
|
32
|
+
zoom?: number; /** Minimal and maximal latitudes and longitudes */
|
|
33
|
+
bounds?: GeoBounds;
|
|
34
|
+
projection?: Projection;
|
|
35
|
+
lang?: string;
|
|
36
|
+
};
|
|
37
|
+
type RenderCallback = (map: MapArea) => void;
|
|
38
|
+
declare class MapArea {
|
|
39
|
+
/** Map parameters */
|
|
40
|
+
_p: MapAreaOptions;
|
|
41
|
+
/** Container */
|
|
42
|
+
_c: HTMLElement | undefined;
|
|
43
|
+
/** Pixel coordinates of the center */
|
|
44
|
+
_cc: PixelCoords | undefined;
|
|
45
|
+
/** Render callbacks */
|
|
46
|
+
_r: Set<RenderCallback>;
|
|
47
|
+
/** Render timeout */
|
|
48
|
+
_t: ReturnType<typeof setTimeout> | null;
|
|
49
|
+
constructor(options: MapAreaOptions);
|
|
50
|
+
render(): void;
|
|
51
|
+
onRender(callback: RenderCallback, skipInitialCall?: boolean): () => void;
|
|
52
|
+
getOptions(): MapAreaOptions;
|
|
53
|
+
setOptions(options: MapAreaOptions): void;
|
|
54
|
+
get container(): HTMLElement;
|
|
55
|
+
get box(): BoxDimensions;
|
|
56
|
+
get bounds(): GeoBounds;
|
|
57
|
+
set bounds(value: GeoBounds);
|
|
58
|
+
get center(): GeoCoords;
|
|
59
|
+
set center(value: GeoCoords);
|
|
60
|
+
get centerCoords(): PixelCoords;
|
|
61
|
+
get zoom(): number;
|
|
62
|
+
set zoom(value: number);
|
|
63
|
+
get projection(): Projection;
|
|
64
|
+
set projection(value: Projection);
|
|
65
|
+
get lang(): string;
|
|
66
|
+
set lang(value: string);
|
|
67
|
+
inPixelBounds(x: number, y: number): boolean;
|
|
68
|
+
inBounds(lat: number, lon: number): boolean;
|
|
69
|
+
canMoveTo(lat: number, lon: number): boolean;
|
|
70
|
+
/** Converts geographic coordinates to pixel coordinates. */
|
|
71
|
+
toPixelCoords(lat: number, lon: number): PixelCoords;
|
|
72
|
+
/** Converts pixel coordinates to geographic coordinates. */
|
|
73
|
+
toGeoCoords(x: number, y: number): GeoCoords;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type IgnoredElement = string | ((element: HTMLElement) => boolean);
|
|
77
|
+
|
|
78
|
+
type MapAreaClickEvent = {
|
|
79
|
+
x: number;
|
|
80
|
+
y: number;
|
|
81
|
+
lat: number;
|
|
82
|
+
lon: number;
|
|
83
|
+
originalEvent: MouseEvent | TouchEvent;
|
|
84
|
+
};
|
|
85
|
+
type MapAreaClickCallback = (event: MapAreaClickEvent) => void;
|
|
86
|
+
/**
|
|
87
|
+
* Adds a click listener to the map and returns a function
|
|
88
|
+
* removing the added click listener.
|
|
89
|
+
*/
|
|
90
|
+
declare function addClickListener(map: MapArea, callback: MapAreaClickCallback, ignored?: IgnoredElement): () => void;
|
|
91
|
+
|
|
92
|
+
declare function addNavigation(map: MapArea): void;
|
|
93
|
+
|
|
94
|
+
type PersistenceOptions = {
|
|
95
|
+
storageKey?: string;
|
|
96
|
+
session?: boolean;
|
|
97
|
+
};
|
|
98
|
+
declare function addPersistence(map: MapArea, {
|
|
99
|
+
storageKey,
|
|
100
|
+
session
|
|
101
|
+
}?: PersistenceOptions): {
|
|
102
|
+
/**
|
|
103
|
+
* Restores map options from the browser storage, or saves the
|
|
104
|
+
* current map options to the storage if the storage entry is empty.
|
|
105
|
+
*/
|
|
106
|
+
sync: () => void; /** Saves the current map options to the browser storage. */
|
|
107
|
+
write: () => void; /** Enables and disables the persistence. */
|
|
108
|
+
toggle: (value?: boolean) => void; /** Resets the map to the initial options. */
|
|
109
|
+
reset: () => void;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
type MapAreaResizeEvent = {
|
|
113
|
+
inlineSize?: number;
|
|
114
|
+
blockSize?: number;
|
|
115
|
+
width?: number;
|
|
116
|
+
height?: number;
|
|
117
|
+
};
|
|
118
|
+
type MapAreaResizeCallback = (event: MapAreaResizeEvent) => void;
|
|
119
|
+
declare function addResizeObserver(map: MapArea, callback?: MapAreaResizeCallback): () => void;
|
|
120
|
+
|
|
121
|
+
type LayerOptions = {
|
|
122
|
+
id?: string;
|
|
123
|
+
className?: string; /** CSS [`inset`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/inset) */
|
|
124
|
+
inset?: string;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
declare function addShape(map: MapArea, coords: GeoCoords[], layerOptions?: LayerOptions): void;
|
|
128
|
+
|
|
129
|
+
type Vertex<T> = {
|
|
130
|
+
id: string;
|
|
131
|
+
coords: T;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
type GeoVertex = Vertex<GeoCoords>;
|
|
135
|
+
|
|
136
|
+
type ShapeEditorOptions = {
|
|
137
|
+
onUpdate?: (shape: GeoVertex[]) => void;
|
|
138
|
+
ignoreClicks?: IgnoredElement;
|
|
139
|
+
};
|
|
140
|
+
declare function addShapeEditor(map: MapArea, options?: ShapeEditorOptions): void;
|
|
141
|
+
|
|
142
|
+
type DynamicString = string | ((map: MapArea) => string);
|
|
143
|
+
|
|
144
|
+
type MapAreaTileOptions = LayerOptions & {
|
|
145
|
+
/**
|
|
146
|
+
* Tile URL, either a string with placeholders (`{x}` and `{y}` for the
|
|
147
|
+
* tile indices, `{z}` for the zoom level, `{lang}` for the map language)
|
|
148
|
+
* or a function of `(map, x, y) => string` returning a fixed string URL.
|
|
149
|
+
*/
|
|
150
|
+
url?: string | ((map: MapArea, xIndex: number, yIndex: number) => string); /** Maximum retry count per tile. */
|
|
151
|
+
retries?: number; /** URL to be used instead of a tile that failed to load. */
|
|
152
|
+
error?: DynamicString; /** Tile size. */
|
|
153
|
+
size?: number;
|
|
154
|
+
/**
|
|
155
|
+
* Margin in pixels, or a tuple of an x- and y-margin, to be tiled
|
|
156
|
+
* outside the viewport.
|
|
157
|
+
*/
|
|
158
|
+
margin?: number | [number, number]; /** Attribution HTML content. */
|
|
159
|
+
attribution?: DynamicString; /** Attribution's CSS [`inset`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/inset). */
|
|
160
|
+
attributionInset?: string;
|
|
161
|
+
};
|
|
162
|
+
declare function addTiles(map: MapArea, options?: MapAreaTileOptions): HTMLElement;
|
|
163
|
+
|
|
164
|
+
type ZoomControlOptions = {
|
|
165
|
+
/** Minimal zoom value */min?: number; /** Maximal zoom value */
|
|
166
|
+
max?: number; /** HTML content of the zoom-in button */
|
|
167
|
+
plus?: string; /** HTML content of the zoom-out button */
|
|
168
|
+
minus?: string;
|
|
169
|
+
};
|
|
170
|
+
declare function addZoomControls(map: MapArea, options?: ZoomControlOptions): HTMLElement;
|
|
171
|
+
|
|
172
|
+
type PixelVertex = Vertex<PixelCoords>;
|
|
173
|
+
|
|
174
|
+
type ShapeLayerOptions = LayerOptions & {
|
|
175
|
+
markers?: boolean;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
declare function getBounds(coords: GeoCoords[]): GeoBounds;
|
|
179
|
+
|
|
180
|
+
declare function getCenter(coords: GeoCoords[]): GeoCoords;
|
|
181
|
+
|
|
182
|
+
declare function getId(): string;
|
|
183
|
+
|
|
184
|
+
declare function getLayer(map: MapArea, {
|
|
185
|
+
id,
|
|
186
|
+
className,
|
|
187
|
+
inset
|
|
188
|
+
}?: LayerOptions): HTMLElement;
|
|
189
|
+
|
|
190
|
+
declare function getVicinity(x: GeoCoords | GeoCoords[] | GeoBounds, [dLat, dLon]?: GeoCoords): GeoBounds;
|
|
191
|
+
|
|
192
|
+
declare function isGeoCoords(x: unknown): x is GeoCoords;
|
|
193
|
+
|
|
194
|
+
declare function resolveString(map: MapArea, x: DynamicString | undefined): string | undefined;
|
|
195
|
+
|
|
196
|
+
declare function toPrecision(x: number, n: number): string;
|
|
197
|
+
|
|
198
|
+
export { BoxDimensions, DEG, DynamicString, GeoBounds, GeoCoords, GeoVertex, LayerOptions, MAX_LAT, MAX_LON, MIN_LAT, MIN_LON, MapArea, MapAreaClickCallback, MapAreaClickEvent, MapAreaOptions, MapAreaResizeCallback, MapAreaResizeEvent, MapAreaTileOptions, PersistenceOptions, PixelCoords, PixelVertex, Projection, RAD, RenderCallback, ShapeEditorOptions, ShapeLayerOptions, ZoomControlOptions, addClickListener, addNavigation, addPersistence, addResizeObserver, addShape, addShapeEditor, addTiles, addZoomControls, eccentricityMap, getBounds, getCenter, getId, getLayer, getVicinity, isGeoCoords, resolveString, toPrecision };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const{PI:e}=Math,t=e/180,n=180/e,r={sphM:0,WGS84:.0818191908426},i=-85.05,a=85.05,o=-180,s=180,{PI:c,sin:l,tan:u,atan:d,pow:f,exp:p,log:m,abs:h}=Math;var g=class{_p;_c;_cc;_r=new Set;_t=null;constructor(e){this._p=e,this.container.style=`position: relative; overflow: hidden;`,this.container.dataset.type=`maparea`}render(){for(let e of this._r)e(this)}onRender(e,t=!1){return this._r.add(e),t||e(this),()=>{this._r.delete(e)}}getOptions(){return{...this._p,center:this.center,bounds:this.bounds}}setOptions(e){this._p={...this._p,...e},delete this._c,delete this._cc,this.render()}get container(){if(!this._c){let e=document.querySelector(this._p.container);if(!e)throw Error(`missing container element`);this._c=e}return this._c}get box(){let e=this.container.getBoundingClientRect();return{x:e.left,y:e.top,w:e.width,h:e.height}}get bounds(){let e=this._p.bounds;return e?{...e}:{}}set bounds(e){this._p.bounds=e}get center(){let e=this._p.center;return e?e.slice():[0,0]}set center(e){this._p.center=e,delete this._cc,this.render()}get centerCoords(){if(!this._cc){let[e,t]=this.center;this._cc=this.toPixelCoords(e,t)}return this._cc}get zoom(){return this._p.zoom??15}set zoom(e){this._p.zoom=e,this._t&&=(clearTimeout(this._t),null),this._t=setTimeout(()=>{this._t=null,delete this._cc,this.render()},150)}get projection(){return this._p.projection??`WGS84`}set projection(e){this._p.projection=e,this.render()}get lang(){return this._p.lang??``}set lang(e){this._p.lang=e,this.render()}inPixelBounds(e,t){let[n,r]=this.toGeoCoords(e,t);return this.inBounds(n,r)}inBounds(e,t){let{minLat:n=i,maxLat:r=a,minLon:s=o,maxLon:c=180}=this.bounds;return e>=n&&e<=r&&t>=s&&t<=c}canMoveTo(e,t){if(!this.inBounds(e,t))return!1;let{w:n,h:r}=this.box;if(n===0||r===0)return!1;let[i,a]=this.toPixelCoords(e,t);return this.inPixelBounds(i-n/2,a-r/2)&&this.inPixelBounds(i+n/2,a+r/2)}toPixelCoords(e,n){let i=r[this.projection],a=f(2,this.zoom+7)/c,o=e*t,s=i===0?1:f((1-i*l(o))/(1+i*l(o)),i/2);return[a*(c+n*t),a*(c-m(s*u(c/4+o/2)))]}toGeoCoords(e,t){let o=r[this.projection],s=f(2,this.zoom+7)/c,l=(e/s-c)*n,u;if(o===0)u=2*(d(p(c-t/s))-c/4)*n;else{u=i;let e=a-i,n=NaN,r=0;for(;(Number.isNaN(n)||h(n-t)>.1)&&r++<30;)e/=2,n=this.toPixelCoords(u+e,l)[1],n>t&&(u+=e)}return(l<-180||l>=180)&&(l=(l+180)%360-180),(u<-90||u>=90)&&(u=(u+90)%180-90),[u,l]}};function _(e,t){return t===void 0||!(e instanceof HTMLElement)?!1:typeof t==`function`&&t(e)||typeof t==`string`&&e.closest(t)!==null}function v(e,t,n){let r=Date.now(),i=()=>{r=Date.now()},a=i=>{if(_(i.target,n)||Date.now()-r>150)return;let{box:a,centerCoords:[o,s]}=e,c=i.clientX-a.x,l=i.clientY-a.y,[u,d]=e.toGeoCoords(c-.5*a.w+o,l-.5*a.h+s);t({x:c,y:l,lat:u,lon:d,originalEvent:i})};return e.container.addEventListener(`mousedown`,i),e.container.addEventListener(`mouseup`,a),()=>{e.container.removeEventListener(`mousedown`,i),e.container.removeEventListener(`mouseup`,a)}}function y(e,{onStart:t,onMove:n,onEnd:r,wheel:i}={}){let a=null,o=null,s=Date.now(),c=!1,l=null,u=null;function d(e,t,r=100){u!==null&&(clearTimeout(u),u=null),l!==null&&(cancelAnimationFrame(l),l=null);let i=Date.now();i-s<r||(a!==null&&(a-=e),o!==null&&(o-=t),s=i,l=requestAnimationFrame(()=>{l=null,(e!==0||t!==0)&&n?.(e,t)}))}function f(e,t,n){a!==null&&o!==null&&d(a-e,o-t,n)}function p(n,r){c=!0,t?.(),n!==void 0&&(a=n),r!==void 0&&(o=r),n!==void 0&&r!==void 0&&(e.dataset.dragged=`true`),s=Date.now()}function m(t,n){c=!1,u=null,t!==void 0&&n!==void 0&&f(t,n),delete e.dataset.dragged,a=null,o=null,requestAnimationFrame(()=>{r?.()})}e.dataset.draggable=`true`;let h=null,g=null;e.addEventListener(`mousedown`,t=>{t.preventDefault(),!h&&(p(t.clientX,t.clientY),h=e=>{e.preventDefault(),f(e.clientX,e.clientY)},e.addEventListener(`mousemove`,h))}),e.addEventListener(`mouseup`,t=>{t.preventDefault(),h&&=(m(t.clientX,t.clientY),e.removeEventListener(`mousemove`,h),null)}),e.addEventListener(`touchstart`,t=>{t.preventDefault(),!g&&(p(t.touches[0]?.pageX,t.touches[0]?.pageY),g=e=>{e.preventDefault(),f(e.touches[0]?.pageX,e.touches[0]?.pageY)},e.addEventListener(`touchmove`,g))}),e.addEventListener(`touchend`,t=>{t.preventDefault(),g&&=(m(t.touches[0]?.pageX,t.touches[0]?.pageY),e.removeEventListener(`touchmove`,g),null)}),e.addEventListener(`touchcancel`,t=>{t.preventDefault(),g&&=(m(t.touches[0]?.pageX,t.touches[0]?.pageY),e.removeEventListener(`touchmove`,g),null)}),i&&e.addEventListener(`wheel`,e=>{e.preventDefault(),c||p(),e.shiftKey?d(e.deltaY,e.deltaX,10):d(e.deltaX,e.deltaY,10),u=setTimeout(()=>{m()},200)})}function b(e){let t=0,n=0;y(e.container,{onStart(){[t,n]=e.centerCoords},onMove(r,i){let a=t+r,o=n+i,s=e.toGeoCoords(a,o);e.canMoveTo(...s)&&(e.center=s,t=a,n=o)},wheel:!0})}function x(e,{storageKey:t,session:n=!1}={}){let r=n?window.sessionStorage:window.localStorage,i=t??[`maparea`,e.container.id].filter(Boolean).join(`#`),a=e.getOptions(),o=!1,s=()=>{try{r.setItem(i,JSON.stringify(e.getOptions()))}catch{}},c=()=>{try{let t=r.getItem(i);t===null?s():e.setOptions(JSON.parse(t))}catch{}},l=e=>{o=e===void 0?!o:e},u=()=>{e.setOptions(a)};c();let d=null;return e.onRender(()=>{o||(d&&=(clearTimeout(d),null),d=setTimeout(()=>{d=null,s()},350))}),{sync:c,write:s,toggle:l,reset:u}}function S(e,t){let n=new ResizeObserver(n=>{for(let r of n)if(r.contentBoxSize){if(t)if(r.contentBoxSize){let{inlineSize:e,blockSize:n}=r.contentBoxSize[0]??r.contentBoxSize;t({inlineSize:e,blockSize:n})}else{let{width:e,height:n}=r.contentRect;t({width:e,height:n})}e.render()}});return n.observe(e.container),()=>{n.unobserve(e.container)}}function C(){return Math.random().toString(36).slice(2)}function w(e,{id:t,className:n,inset:r=`0`}={}){let i=`${n?`.${n}`:``}${t?`#${t}`:``}`,a=document.querySelector(i);return a||(a=document.createElement(`div`),a.className=`${n?`${n} `:``}layer`,a.id=t??`layer-${C()}`,a.style=`position: absolute; inset: ${r};`,e.container.append(a)),a}function T(e,t){return e.toFixed(t).replace(/\.(\d*[^0])0+$/,`.$1`).replace(/\.0+$/,``)}const E=`http://www.w3.org/2000/svg`,D={className:`shape`};function O(e,t,n){let r=w(e,n??D);r.toggleAttribute(`hidden`,t.length===0);let i=r.querySelector(`svg`);i||(i=document.createElementNS(E,`svg`),i.setAttribute(`xmlns`,E),r.append(i));let a=A(i,t.map(({id:t,coords:[n,r]})=>({id:t,coords:e.toPixelCoords(n,r)})),n);if(a){let{xMin:t,xMax:n,yMin:r,yMax:o}=a,{centerCoords:[s,c],box:l}=e,u=T(n-t,3),d=T(o-r,3),f=T(.5*l.w+t-s,2),p=T(.5*l.h+r-c,2);i.setAttribute(`viewBox`,`0 0 ${u} ${d}`),i.setAttribute(`width`,u),i.setAttribute(`height`,d),i.setAttribute(`style`,`position: absolute; left: ${f}px; top: ${p}px;`)}return r}function k(e){return parseFloat(window.getComputedStyle(e).strokeWidth||e.getAttribute(`stroke-width`)||`1`)}function A(e,t,n){if(t.length===0){e.innerHTML=``;return}let r=e.querySelector(`path`);r||(r=document.createElementNS(E,`path`),e.append(r));let i=t[0].coords[0],a=i,o=t[0].coords[1],s=o,c=``;for(let{coords:[e,n]}of t)i>e&&(i=e),a<e&&(a=e),o>n&&(o=n),s<n&&(s=n);i-=10,a+=10,o-=10,s+=10;let l=Array.from(e.querySelectorAll(`.marker`)),u=0;for(let{id:a,coords:[s,d]}of t){let t=T(s-i,3),f=T(d-o,3);if(c+=`${c?` L`:`M`} ${t} ${f}`,n?.markers){let n=l[u++];n||(n=document.createElementNS(E,`circle`),n.setAttribute(`class`,`marker`),n.setAttribute(`r`,String(1.5*k(r))),e.append(n)),n.setAttribute(`cx`,t),n.setAttribute(`cy`,f),n.dataset.id=a}}for(let e=u;e<l.length;e++)l[e].remove();return r.setAttribute(`d`,c),{xMin:i,xMax:a,yMin:o,yMax:s}}function j(e,t,n){let r={id:`shape-${C()}`,className:`shape`},i=t.map(e=>({id:C(),coords:e}));e.onRender(()=>{O(e,i,{...r,...n})})}function M(e,t){let n=[],r=null,i={id:`shape-editor-${C()}`,className:`shape-editor`,markers:!0},a=()=>{O(e,n,i),t?.onUpdate?.(n)};v(e,({lat:e,lon:t,originalEvent:i})=>{let o=(i.target?.closest(`.marker`))?.dataset.id;if(o&&r){clearTimeout(r),r=null,n=n.filter(({id:e})=>e!==o),a();return}let s=()=>{n.push({id:C(),coords:[e,t]}),a(),r=null};o?r=setTimeout(s,250):s()},t?.ignoreClicks),e.onRender(a)}function N(e,t){return typeof t==`function`?t(e):t}const{floor:P,ceil:F}=Math;function I(e,t,n){return`${t},${n},${e.zoom},${e.lang}`}function L(e,t,n,{size:r=256,url:i,retries:a=0,error:o}){let s=new Image,c=0,l=(r,a)=>i?typeof i==`function`?i(e,t,n):i.replaceAll(`{x}`,String(r)).replaceAll(`{y}`,String(a)).replaceAll(`{z}`,String(e.zoom)).replaceAll(`{lang}`,e.lang):``;return s.width=r,s.height=r,s.src=l(t,n),s.dataset.id=I(e,t,n),s.style=`position: absolute;`,s.onerror=()=>{c++;let{src:i}=s;if(!i||c>a){let t=N(e,o);t&&(s.onerror=()=>{},s.src=t);return}let u=t*r,d=n*r,f=e.toPixelCoords(...e.toGeoCoords(u,d))[0],p=P(f/r);if(t!==p)s.src=l(p,n);else{let e=new URL(i);e.searchParams.set(`_t`,String(Date.now())),s.src=e.href}},s}function R(e,t,{attribution:n,attributionInset:r=`auto 0 0 auto`}){let i=t.querySelector(`.attribution`),a=N(e,n);return a?(i||(i=document.createElement(`div`),i.className=`attribution`,i.style=`position: absolute; inset: ${r};`,t.append(i)),i.innerHTML!==a&&(i.innerHTML=a),i):(i&&i.remove(),null)}function z(e){return e.querySelectorAll(`img[data-id]`)}function B(e,t){return e.querySelector(`img[data-id="${t}"]`)}function V(e,t={}){let{id:n=`tiles-${C()}`,className:r=`tiles`}=t,i=w(e,{id:n,className:r});return e.onRender(()=>{let n=R(e,i,t),{box:{w:r,h:a},centerCoords:[o,s]}=e,{size:c=256,margin:l=0}=t,u=Array.isArray(l)?l[0]:l,d=Array.isArray(l)?l[1]:l,f=F((r+2*u)/c),p=F((a+2*d)/c),m=P(o/c),h=P(s/c),g=null,_=new Set,v=``;for(let l=0;l<=f;l++){let u=m+(l%2==0?-1:1)*P(l/2);for(let l=0;l<=p;l++){let d=h+(l%2==0?-1:1)*P(l/2);v=I(e,u,d),g=B(i,v),g||(g=L(e,u,d,t),i.insertBefore(g,n));let f=.5*r+u*c-o,p=.5*a+d*c-s;g.style.left=`${T(f,2)}px`,g.style.top=`${T(p,2)}px`,_.add(v)}}for(let e of z(i)){let{id:t}=e.dataset;t&&!_.has(t)&&e.remove()}}),i}function H(e,t={}){let n=w(e,{className:`zoom-controls`,inset:`0 0 auto auto`}),{min:r,max:i,plus:a=`➕`,minus:o=`➖`}=t,s=document.createElement(`button`);s.dataset.id=`plus`,s.innerHTML=a;let c=document.createElement(`button`);c.dataset.id=`minus`,c.innerHTML=o;let l=()=>{i!==void 0&&s.toggleAttribute(`disabled`,e.zoom+1>i),r!==void 0&&c.toggleAttribute(`disabled`,e.zoom-1<r)};return n.addEventListener(`click`,t=>{let n=t.target?.closest(`button`);n&&(t.stopPropagation(),e.zoom+=n.dataset.id===`minus`?-1:1,l())}),e.onRender(l),n.append(s,c),n}function U(e){if(e.length===0)return{};let t=e[0][0],n=t,r=e[0][1],i=r;for(let[a,o]of e)t>a&&(t=a),n<a&&(n=a),r>o&&(r=o),i<o&&(i=o);return{minLat:t,maxLat:n,minLon:r,maxLon:i}}function W(e){let{minLat:t,maxLat:n,minLon:r,maxLon:i}=U(e);return[t===void 0||n===void 0?0:(n+t)/2,r===void 0||i===void 0?0:(i+r)/2]}function G(e){return Array.isArray(e)&&e.length===2&&typeof e[0]==`number`&&typeof e[1]==`number`}const K=[.005,.018];function q(e){return Array.isArray(e)?G(e)?{minLat:e[0],maxLat:e[0],minLon:e[1],maxLon:e[1]}:U(e):e}function J(e,[t,n]=K){let{minLat:r=0,maxLat:i=0,minLon:a=0,maxLon:o=0}=q(e);return{minLat:r-t,maxLat:i+t,minLon:a-n,maxLon:o+n}}export{n as DEG,a as MAX_LAT,s as MAX_LON,i as MIN_LAT,o as MIN_LON,g as MapArea,t as RAD,v as addClickListener,b as addNavigation,x as addPersistence,S as addResizeObserver,j as addShape,M as addShapeEditor,V as addTiles,H as addZoomControls,r as eccentricityMap,U as getBounds,W as getCenter,C as getId,w as getLayer,J as getVicinity,G as isGeoCoords,N as resolveString,T as toPrecision};
|
package/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export * from "./src/MapArea/const.ts";
|
|
2
|
+
export * from "./src/MapArea/index.ts";
|
|
3
|
+
export * from "./src/plugins/addClickListener.ts";
|
|
4
|
+
export * from "./src/plugins/addNavigation.ts";
|
|
5
|
+
export * from "./src/plugins/addPersistence.ts";
|
|
6
|
+
export * from "./src/plugins/addResizeObserver.ts";
|
|
7
|
+
export * from "./src/plugins/addShape.ts";
|
|
8
|
+
export * from "./src/plugins/addShapeEditor.ts";
|
|
9
|
+
export * from "./src/plugins/addTiles.ts";
|
|
10
|
+
export * from "./src/plugins/addZoomControls.ts";
|
|
11
|
+
export * from "./src/types/BoxDimensions.ts";
|
|
12
|
+
export * from "./src/types/DynamicString.ts";
|
|
13
|
+
export * from "./src/types/GeoBounds.ts";
|
|
14
|
+
export * from "./src/types/GeoCoords.ts";
|
|
15
|
+
export * from "./src/types/GeoVertex.ts";
|
|
16
|
+
export * from "./src/types/LayerOptions.ts";
|
|
17
|
+
export * from "./src/types/PixelCoords.ts";
|
|
18
|
+
export * from "./src/types/PixelVertex.ts";
|
|
19
|
+
export * from "./src/types/Projection.ts";
|
|
20
|
+
export * from "./src/types/ShapeLayerOptions.ts";
|
|
21
|
+
export * from "./src/utils/getBounds.ts";
|
|
22
|
+
export * from "./src/utils/getCenter.ts";
|
|
23
|
+
export * from "./src/utils/getId.ts";
|
|
24
|
+
export * from "./src/utils/getLayer.ts";
|
|
25
|
+
export * from "./src/utils/getVicinity.ts";
|
|
26
|
+
export * from "./src/utils/isGeoCoords.ts";
|
|
27
|
+
export * from "./src/utils/resolveString.ts";
|
|
28
|
+
export * from "./src/utils/toPrecision.ts";
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "maparea",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"preversion": "npm run shape",
|
|
10
|
+
"shape": "npx codeshape --minify",
|
|
11
|
+
"start": "npx @t8/serve test --spa -b --watch"
|
|
12
|
+
},
|
|
13
|
+
"author": "axtk"
|
|
14
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["./src", "./test", "./index.ts"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": true,
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
7
|
+
"target": "esnext",
|
|
8
|
+
"module": "nodenext",
|
|
9
|
+
"moduleResolution": "nodenext",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"strict": true
|
|
12
|
+
}
|
|
13
|
+
}
|