@takram/three-geospatial 0.0.1-alpha.1
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/README.md +15 -0
- package/build/index.cjs +43 -0
- package/build/index.js +932 -0
- package/build/r3f.cjs +1 -0
- package/build/r3f.js +38 -0
- package/build/shared.cjs +1 -0
- package/build/shared.js +198 -0
- package/package.json +42 -0
- package/src/ArrayBufferLoader.ts +35 -0
- package/src/DataLoader.ts +114 -0
- package/src/Ellipsoid.ts +128 -0
- package/src/EllipsoidGeometry.ts +107 -0
- package/src/Geodetic.ts +160 -0
- package/src/PointOfView.ts +169 -0
- package/src/Rectangle.ts +97 -0
- package/src/TileCoordinate.test.ts +38 -0
- package/src/TileCoordinate.ts +112 -0
- package/src/TilingScheme.test.ts +63 -0
- package/src/TilingScheme.ts +76 -0
- package/src/TypedArrayLoader.ts +53 -0
- package/src/assertions.ts +13 -0
- package/src/bufferGeometry.ts +62 -0
- package/src/helpers/projectOnEllipsoidSurface.ts +72 -0
- package/src/index.ts +25 -0
- package/src/math.ts +41 -0
- package/src/r3f/EastNorthUpFrame.tsx +52 -0
- package/src/r3f/EllipsoidMesh.tsx +36 -0
- package/src/r3f/index.ts +2 -0
- package/src/shaders/depth.glsl +15 -0
- package/src/shaders/packing.glsl +20 -0
- package/src/shaders/transform.glsl +12 -0
- package/src/typedArray.ts +76 -0
- package/src/types.ts +54 -0
- package/types/ArrayBufferLoader.d.ts +5 -0
- package/types/DataLoader.d.ts +67 -0
- package/types/Ellipsoid.d.ts +18 -0
- package/types/EllipsoidGeometry.d.ts +13 -0
- package/types/Geodetic.d.ts +37 -0
- package/types/PointOfView.d.ts +20 -0
- package/types/Rectangle.d.ts +27 -0
- package/types/TileCoordinate.d.ts +21 -0
- package/types/TilingScheme.d.ts +21 -0
- package/types/TypedArrayLoader.d.ts +16 -0
- package/types/assertions.d.ts +4 -0
- package/types/bufferGeometry.d.ts +5 -0
- package/types/helpers/projectOnEllipsoidSurface.d.ts +6 -0
- package/types/index.d.ts +18 -0
- package/types/math.d.ts +13 -0
- package/types/r3f/EastNorthUpFrame.d.ts +15 -0
- package/types/r3f/EllipsoidMesh.d.ts +13 -0
- package/types/r3f/index.d.ts +2 -0
- package/types/typedArray.d.ts +10 -0
- package/types/types.d.ts +22 -0
package/build/r3f.cjs
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react/jsx-runtime"),i=require("react"),l=require("three"),n=require("./shared.cjs"),f=require("@react-three/fiber"),m=new l.Matrix4,d=new n.Geodetic,E=new l.Vector3;class h extends l.Group{set(t,e,r,o=n.Ellipsoid.WGS84){const s=d.set(t,e,r).toECEF(E);o.getEastNorthUpFrame(s,m).decompose(this.position,this.quaternion,this.scale)}}const x=i.forwardRef(function({longitude:t,latitude:e,height:r=0,ellipsoid:o=n.Ellipsoid.WGS84,children:s},p){const a=i.useMemo(()=>new h,[]);return i.useEffect(()=>{a.set(t,e,r,o)},[t,e,r,o,a]),u.jsx("primitive",{ref:p,object:a,children:s})});function G(c){return t=>{c.forEach(e=>{typeof e=="function"?e(t):e!=null&&(e.current=t)})}}const S=i.forwardRef(function({args:t,children:e,...r},o){const s=i.useRef(null);return f.extend({EllipsoidGeometry:n.EllipsoidGeometry}),u.jsxs("mesh",{ref:G([s,o]),...r,children:[u.jsx("ellipsoidGeometry",{args:t}),e]})});exports.EastNorthUpFrame=x;exports.EllipsoidMesh=S;
|
package/build/r3f.js
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
import { jsx as a, jsxs as f } from "react/jsx-runtime";
|
2
|
+
import { forwardRef as m, useMemo as u, useEffect as l, useRef as E } from "react";
|
3
|
+
import { Group as h, Matrix4 as d, Vector3 as x } from "three";
|
4
|
+
import { E as p, G, a as w } from "./shared.js";
|
5
|
+
import { extend as F } from "@react-three/fiber";
|
6
|
+
const S = /* @__PURE__ */ new d(), N = /* @__PURE__ */ new G(), U = /* @__PURE__ */ new x();
|
7
|
+
class j extends h {
|
8
|
+
set(o, t, r, e = p.WGS84) {
|
9
|
+
const s = N.set(o, t, r).toECEF(U);
|
10
|
+
e.getEastNorthUpFrame(s, S).decompose(this.position, this.quaternion, this.scale);
|
11
|
+
}
|
12
|
+
}
|
13
|
+
const g = /* @__PURE__ */ m(function({ longitude: o, latitude: t, height: r = 0, ellipsoid: e = p.WGS84, children: s }, n) {
|
14
|
+
const c = u(() => new j(), []);
|
15
|
+
return l(() => {
|
16
|
+
c.set(o, t, r, e);
|
17
|
+
}, [o, t, r, e, c]), /* @__PURE__ */ a("primitive", { ref: n, object: c, children: s });
|
18
|
+
});
|
19
|
+
function y(i) {
|
20
|
+
return (o) => {
|
21
|
+
i.forEach((t) => {
|
22
|
+
typeof t == "function" ? t(o) : t != null && (t.current = o);
|
23
|
+
});
|
24
|
+
};
|
25
|
+
}
|
26
|
+
const q = /* @__PURE__ */ m(
|
27
|
+
function({ args: o, children: t, ...r }, e) {
|
28
|
+
const s = E(null);
|
29
|
+
return F({ EllipsoidGeometry: w }), /* @__PURE__ */ f("mesh", { ref: y([s, e]), ...r, children: [
|
30
|
+
/* @__PURE__ */ a("ellipsoidGeometry", { args: o }),
|
31
|
+
t
|
32
|
+
] });
|
33
|
+
}
|
34
|
+
);
|
35
|
+
export {
|
36
|
+
g as EastNorthUpFrame,
|
37
|
+
q as EllipsoidMesh
|
38
|
+
};
|
package/build/shared.cjs
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
"use strict";var P=Object.defineProperty;var j=(p,t,e)=>t in p?P(p,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):p[t]=e;var g=(p,t,e)=>j(p,typeof t!="symbol"?t+"":t,e);const s=require("three"),W=new s.Vector3;function G(p,t,e=new s.Vector3,r){const{x:i,y:n,z:c}=p,o=t.x,a=t.y,y=t.z,d=i*i*o,u=n*n*a,m=c*c*y,l=d+u+m,h=Math.sqrt(1/l);if(!Number.isFinite(h))return;const f=W.copy(p).multiplyScalar(h);if(l<((r==null?void 0:r.centerTolerance)??.1))return e.copy(f);const A=f.multiply(t).multiplyScalar(2);let M=(1-h)*p.length()/(A.length()/2),z=0,w,x,V,E;do{M-=z,w=1/(1+M*o),x=1/(1+M*a),V=1/(1+M*y);const v=w*w,L=x*x,T=V*V,B=v*w,C=L*x,D=T*V;E=d*v+u*L+m*T-1,z=E/((d*B*o+u*C*a+m*D*y)*-2)}while(Math.abs(E)>1e-12);return e.set(i*w,n*x,c*V)}const b=new s.Vector3,U=new s.Vector3,O=new s.Vector3,S=class S{constructor(t,e,r){g(this,"radii");this.radii=new s.Vector3(t,e,r)}get minimumRadius(){return Math.min(this.radii.x,this.radii.y,this.radii.z)}get maximumRadius(){return Math.max(this.radii.x,this.radii.y,this.radii.z)}reciprocalRadii(t=new s.Vector3){const{x:e,y:r,z:i}=this.radii;return t.set(1/e,1/r,1/i)}reciprocalRadiiSquared(t=new s.Vector3){const{x:e,y:r,z:i}=this.radii;return t.set(1/e**2,1/r**2,1/i**2)}projectOnSurface(t,e=new s.Vector3,r){return G(t,this.reciprocalRadiiSquared(),e,r)}getSurfaceNormal(t,e=new s.Vector3){return e.multiplyVectors(this.reciprocalRadiiSquared(b),t).normalize()}getEastNorthUpVectors(t,e=new s.Vector3,r=new s.Vector3,i=new s.Vector3){this.getSurfaceNormal(t,i),e.set(-t.y,t.x,0).normalize(),r.crossVectors(i,e).normalize()}getEastNorthUpFrame(t,e=new s.Matrix4){const r=b,i=U,n=O;return this.getEastNorthUpVectors(t,r,i,n),e.makeBasis(r,i,n).setPosition(t)}getIntersection(t,e=new s.Vector3){const r=this.reciprocalRadii(b),i=U.copy(r).multiply(t.origin),n=O.copy(r).multiply(t.direction),c=i.lengthSq(),o=n.lengthSq(),a=i.dot(n),y=a**2-o*(c-1);if(c===1)return e.copy(t.origin);if(c>1){if(a>=0||y<0)return;const d=Math.sqrt(y),u=(-a-d)/o,m=(-a+d)/o;return t.at(Math.min(u,m),e)}if(c<1){const d=a**2-o*(c-1),u=Math.sqrt(d),m=(-a+u)/o;return t.at(m,e)}if(a<0)return t.at(-a/o,e)}getOsculatingSphereCenter(t,e,r=new s.Vector3){const i=this.radii.x**2,n=b.set(t.x/i,t.y/i,t.z/this.radii.z**2).normalize();return r.copy(n.multiplyScalar(-e).add(t))}};g(S,"WGS84",new S(6378137,6378137,6356752314245179e-9));let q=S;class $ extends s.BufferGeometry{constructor(e=new s.Vector3(1,1,1),r=32,i=16){super();g(this,"type","EllipsoidGeometry");g(this,"parameters");this.parameters={radii:e,longitudeSegments:r,latitudeSegments:i},r=Math.max(3,Math.floor(r)),i=Math.max(2,Math.floor(i));const n=(r+1)*(i+1),c=new s.Vector3,o=new s.Vector3,a=new Float32Array(n*3),y=new Float32Array(n*3),d=new Float32Array(n*2),u=[],m=[];for(let l=0,h=0,f=0,A=0;l<=i;++l){const M=[],z=l/i,w=z*Math.PI;let x=0;l===0?x=.5/r:l===i&&(x=-.5/r);for(let V=0;V<=r;++V,h+=3,f+=2,++A){const E=V/r,v=E*Math.PI*2;c.x=e.x*Math.cos(v)*Math.sin(w),c.y=e.y*Math.sin(v)*Math.sin(w),c.z=e.z*Math.cos(w),a[h]=c.x,a[h+1]=c.y,a[h+2]=c.z,o.copy(c).normalize(),y[h]=o.x,y[h+1]=o.y,y[h+2]=o.z,d[f]=E+x,d[f+1]=1-z,M.push(A)}u.push(M)}for(let l=0;l<i;++l)for(let h=0;h<r;++h){const f=u[l][h+1],A=u[l][h],M=u[l+1][h],z=u[l+1][h+1];l!==0&&m.push(f,A,z),l!==i-1&&m.push(A,M,z)}this.setIndex(m),this.setAttribute("position",new s.BufferAttribute(a,3)),this.setAttribute("normal",new s.BufferAttribute(y,3)),this.setAttribute("uv",new s.BufferAttribute(d,2))}copy(e){return super.copy(e),this.parameters={...e.parameters},this}}const N=new s.Vector3,R=new s.Vector3,I=class I{constructor(t=0,e=0,r=0){this.longitude=t,this.latitude=e,this.height=r}set(t,e,r){return this.longitude=t,this.latitude=e,r!=null&&(this.height=r),this}clone(){return new I(this.longitude,this.latitude,this.height)}copy(t){return this.longitude=t.longitude,this.latitude=t.latitude,this.height=t.height,this}equals(t){return t.longitude===this.longitude&&t.latitude===this.latitude&&t.height===this.height}setLongitude(t){return this.longitude=t,this}setLatitude(t){return this.latitude=t,this}setHeight(t){return this.height=t,this}normalize(){return this.longitude<I.MIN_LONGITUDE&&(this.longitude+=Math.PI*2),this}setFromECEF(t,e){const i=((e==null?void 0:e.ellipsoid)??q.WGS84).reciprocalRadiiSquared(N),n=G(t,i,R,e);if(n==null)throw new Error(`Could not project position to ellipsoid surface: ${t.toArray()}`);const c=N.multiplyVectors(n,i).normalize();this.longitude=Math.atan2(c.y,c.x),this.latitude=Math.asin(c.z);const o=N.subVectors(t,n);return this.height=Math.sign(o.dot(t))*o.length(),this}toECEF(t=new s.Vector3,e){const r=(e==null?void 0:e.ellipsoid)??q.WGS84,i=N.multiplyVectors(r.radii,r.radii),n=Math.cos(this.latitude),c=R.set(n*Math.cos(this.longitude),n*Math.sin(this.longitude),Math.sin(this.latitude)).normalize();return t.multiplyVectors(i,c),t.divideScalar(Math.sqrt(c.dot(t))).add(c.multiplyScalar(this.height))}fromArray(t,e=0){return this.longitude=t[e],this.latitude=t[e+1],this.height=t[e+2],this}toArray(t=[],e=0){return t[e]=this.longitude,t[e+1]=this.latitude,t[e+2]=this.height,t}*[Symbol.iterator](){yield this.longitude,yield this.latitude,yield this.height}};g(I,"MIN_LONGITUDE",-Math.PI),g(I,"MAX_LONGITUDE",Math.PI),g(I,"MIN_LATITUDE",-Math.PI/2),g(I,"MAX_LATITUDE",Math.PI/2);let F=I;exports.Ellipsoid=q;exports.EllipsoidGeometry=$;exports.Geodetic=F;
|
package/build/shared.js
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
var B = Object.defineProperty;
|
2
|
+
var W = (p, t, i) => t in p ? B(p, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : p[t] = i;
|
3
|
+
var z = (p, t, i) => W(p, typeof t != "symbol" ? t + "" : t, i);
|
4
|
+
import { Vector3 as c, Matrix4 as $, BufferGeometry as Q, BufferAttribute as F } from "three";
|
5
|
+
const X = /* @__PURE__ */ new c();
|
6
|
+
function D(p, t, i = new c(), r) {
|
7
|
+
const { x: s, y: n, z: e } = p, a = t.x, o = t.y, y = t.z, d = s * s * a, u = n * n * o, m = e * e * y, l = d + u + m, h = Math.sqrt(1 / l);
|
8
|
+
if (!Number.isFinite(h))
|
9
|
+
return;
|
10
|
+
const f = X.copy(p).multiplyScalar(h);
|
11
|
+
if (l < ((r == null ? void 0 : r.centerTolerance) ?? 0.1))
|
12
|
+
return i.copy(f);
|
13
|
+
const E = f.multiply(t).multiplyScalar(2);
|
14
|
+
let M = (1 - h) * p.length() / (E.length() / 2), I = 0, w, x, g, A;
|
15
|
+
do {
|
16
|
+
M -= I, w = 1 / (1 + M * a), x = 1 / (1 + M * o), g = 1 / (1 + M * y);
|
17
|
+
const q = w * w, L = x * x, T = g * g, G = q * w, P = L * x, j = T * g;
|
18
|
+
A = d * q + u * L + m * T - 1, I = A / ((d * G * a + u * P * o + m * j * y) * -2);
|
19
|
+
} while (Math.abs(A) > 1e-12);
|
20
|
+
return i.set(s * w, n * x, e * g);
|
21
|
+
}
|
22
|
+
const N = /* @__PURE__ */ new c(), U = /* @__PURE__ */ new c(), O = /* @__PURE__ */ new c(), V = class V {
|
23
|
+
constructor(t, i, r) {
|
24
|
+
z(this, "radii");
|
25
|
+
this.radii = new c(t, i, r);
|
26
|
+
}
|
27
|
+
get minimumRadius() {
|
28
|
+
return Math.min(this.radii.x, this.radii.y, this.radii.z);
|
29
|
+
}
|
30
|
+
get maximumRadius() {
|
31
|
+
return Math.max(this.radii.x, this.radii.y, this.radii.z);
|
32
|
+
}
|
33
|
+
reciprocalRadii(t = new c()) {
|
34
|
+
const { x: i, y: r, z: s } = this.radii;
|
35
|
+
return t.set(1 / i, 1 / r, 1 / s);
|
36
|
+
}
|
37
|
+
reciprocalRadiiSquared(t = new c()) {
|
38
|
+
const { x: i, y: r, z: s } = this.radii;
|
39
|
+
return t.set(1 / i ** 2, 1 / r ** 2, 1 / s ** 2);
|
40
|
+
}
|
41
|
+
projectOnSurface(t, i = new c(), r) {
|
42
|
+
return D(
|
43
|
+
t,
|
44
|
+
this.reciprocalRadiiSquared(),
|
45
|
+
i,
|
46
|
+
r
|
47
|
+
);
|
48
|
+
}
|
49
|
+
getSurfaceNormal(t, i = new c()) {
|
50
|
+
return i.multiplyVectors(this.reciprocalRadiiSquared(N), t).normalize();
|
51
|
+
}
|
52
|
+
getEastNorthUpVectors(t, i = new c(), r = new c(), s = new c()) {
|
53
|
+
this.getSurfaceNormal(t, s), i.set(-t.y, t.x, 0).normalize(), r.crossVectors(s, i).normalize();
|
54
|
+
}
|
55
|
+
getEastNorthUpFrame(t, i = new $()) {
|
56
|
+
const r = N, s = U, n = O;
|
57
|
+
return this.getEastNorthUpVectors(t, r, s, n), i.makeBasis(r, s, n).setPosition(t);
|
58
|
+
}
|
59
|
+
getIntersection(t, i = new c()) {
|
60
|
+
const r = this.reciprocalRadii(N), s = U.copy(r).multiply(t.origin), n = O.copy(r).multiply(t.direction), e = s.lengthSq(), a = n.lengthSq(), o = s.dot(n), y = o ** 2 - a * (e - 1);
|
61
|
+
if (e === 1)
|
62
|
+
return i.copy(t.origin);
|
63
|
+
if (e > 1) {
|
64
|
+
if (o >= 0 || y < 0)
|
65
|
+
return;
|
66
|
+
const d = Math.sqrt(y), u = (-o - d) / a, m = (-o + d) / a;
|
67
|
+
return t.at(Math.min(u, m), i);
|
68
|
+
}
|
69
|
+
if (e < 1) {
|
70
|
+
const d = o ** 2 - a * (e - 1), u = Math.sqrt(d), m = (-o + u) / a;
|
71
|
+
return t.at(m, i);
|
72
|
+
}
|
73
|
+
if (o < 0)
|
74
|
+
return t.at(-o / a, i);
|
75
|
+
}
|
76
|
+
getOsculatingSphereCenter(t, i, r = new c()) {
|
77
|
+
const s = this.radii.x ** 2, n = N.set(
|
78
|
+
t.x / s,
|
79
|
+
t.y / s,
|
80
|
+
t.z / this.radii.z ** 2
|
81
|
+
).normalize();
|
82
|
+
return r.copy(n.multiplyScalar(-i).add(t));
|
83
|
+
}
|
84
|
+
};
|
85
|
+
z(V, "WGS84", /* @__PURE__ */ new V(
|
86
|
+
6378137,
|
87
|
+
6378137,
|
88
|
+
6356752314245179e-9
|
89
|
+
));
|
90
|
+
let b = V;
|
91
|
+
class J extends Q {
|
92
|
+
constructor(i = new c(1, 1, 1), r = 32, s = 16) {
|
93
|
+
super();
|
94
|
+
z(this, "type", "EllipsoidGeometry");
|
95
|
+
z(this, "parameters");
|
96
|
+
this.parameters = {
|
97
|
+
radii: i,
|
98
|
+
longitudeSegments: r,
|
99
|
+
latitudeSegments: s
|
100
|
+
}, r = Math.max(3, Math.floor(r)), s = Math.max(2, Math.floor(s));
|
101
|
+
const n = (r + 1) * (s + 1), e = new c(), a = new c(), o = new Float32Array(n * 3), y = new Float32Array(n * 3), d = new Float32Array(n * 2), u = [], m = [];
|
102
|
+
for (let l = 0, h = 0, f = 0, E = 0; l <= s; ++l) {
|
103
|
+
const M = [], I = l / s, w = I * Math.PI;
|
104
|
+
let x = 0;
|
105
|
+
l === 0 ? x = 0.5 / r : l === s && (x = -0.5 / r);
|
106
|
+
for (let g = 0; g <= r; ++g, h += 3, f += 2, ++E) {
|
107
|
+
const A = g / r, q = A * Math.PI * 2;
|
108
|
+
e.x = i.x * Math.cos(q) * Math.sin(w), e.y = i.y * Math.sin(q) * Math.sin(w), e.z = i.z * Math.cos(w), o[h] = e.x, o[h + 1] = e.y, o[h + 2] = e.z, a.copy(e).normalize(), y[h] = a.x, y[h + 1] = a.y, y[h + 2] = a.z, d[f] = A + x, d[f + 1] = 1 - I, M.push(E);
|
109
|
+
}
|
110
|
+
u.push(M);
|
111
|
+
}
|
112
|
+
for (let l = 0; l < s; ++l)
|
113
|
+
for (let h = 0; h < r; ++h) {
|
114
|
+
const f = u[l][h + 1], E = u[l][h], M = u[l + 1][h], I = u[l + 1][h + 1];
|
115
|
+
l !== 0 && m.push(f, E, I), l !== s - 1 && m.push(E, M, I);
|
116
|
+
}
|
117
|
+
this.setIndex(m), this.setAttribute("position", new F(o, 3)), this.setAttribute("normal", new F(y, 3)), this.setAttribute("uv", new F(d, 2));
|
118
|
+
}
|
119
|
+
copy(i) {
|
120
|
+
return super.copy(i), this.parameters = { ...i.parameters }, this;
|
121
|
+
}
|
122
|
+
}
|
123
|
+
const S = /* @__PURE__ */ new c(), R = /* @__PURE__ */ new c(), v = class v {
|
124
|
+
constructor(t = 0, i = 0, r = 0) {
|
125
|
+
this.longitude = t, this.latitude = i, this.height = r;
|
126
|
+
}
|
127
|
+
set(t, i, r) {
|
128
|
+
return this.longitude = t, this.latitude = i, r != null && (this.height = r), this;
|
129
|
+
}
|
130
|
+
clone() {
|
131
|
+
return new v(this.longitude, this.latitude, this.height);
|
132
|
+
}
|
133
|
+
copy(t) {
|
134
|
+
return this.longitude = t.longitude, this.latitude = t.latitude, this.height = t.height, this;
|
135
|
+
}
|
136
|
+
equals(t) {
|
137
|
+
return t.longitude === this.longitude && t.latitude === this.latitude && t.height === this.height;
|
138
|
+
}
|
139
|
+
setLongitude(t) {
|
140
|
+
return this.longitude = t, this;
|
141
|
+
}
|
142
|
+
setLatitude(t) {
|
143
|
+
return this.latitude = t, this;
|
144
|
+
}
|
145
|
+
setHeight(t) {
|
146
|
+
return this.height = t, this;
|
147
|
+
}
|
148
|
+
normalize() {
|
149
|
+
return this.longitude < v.MIN_LONGITUDE && (this.longitude += Math.PI * 2), this;
|
150
|
+
}
|
151
|
+
// See: https://en.wikipedia.org/wiki/Geographic_coordinate_conversion
|
152
|
+
// Reference: https://github.com/CesiumGS/cesium/blob/1.122/packages/engine/Source/Core/Geodetic.js#L119
|
153
|
+
setFromECEF(t, i) {
|
154
|
+
const s = ((i == null ? void 0 : i.ellipsoid) ?? b.WGS84).reciprocalRadiiSquared(S), n = D(
|
155
|
+
t,
|
156
|
+
s,
|
157
|
+
R,
|
158
|
+
i
|
159
|
+
);
|
160
|
+
if (n == null)
|
161
|
+
throw new Error(
|
162
|
+
`Could not project position to ellipsoid surface: ${t.toArray()}`
|
163
|
+
);
|
164
|
+
const e = S.multiplyVectors(n, s).normalize();
|
165
|
+
this.longitude = Math.atan2(e.y, e.x), this.latitude = Math.asin(e.z);
|
166
|
+
const a = S.subVectors(t, n);
|
167
|
+
return this.height = Math.sign(a.dot(t)) * a.length(), this;
|
168
|
+
}
|
169
|
+
// See: https://en.wikipedia.org/wiki/Geographic_coordinate_conversion
|
170
|
+
// Reference: https://github.com/CesiumGS/cesium/blob/1.122/packages/engine/Source/Core/Cartesian3.js#L916
|
171
|
+
toECEF(t = new c(), i) {
|
172
|
+
const r = (i == null ? void 0 : i.ellipsoid) ?? b.WGS84, s = S.multiplyVectors(
|
173
|
+
r.radii,
|
174
|
+
r.radii
|
175
|
+
), n = Math.cos(this.latitude), e = R.set(
|
176
|
+
n * Math.cos(this.longitude),
|
177
|
+
n * Math.sin(this.longitude),
|
178
|
+
Math.sin(this.latitude)
|
179
|
+
).normalize();
|
180
|
+
return t.multiplyVectors(s, e), t.divideScalar(Math.sqrt(e.dot(t))).add(e.multiplyScalar(this.height));
|
181
|
+
}
|
182
|
+
fromArray(t, i = 0) {
|
183
|
+
return this.longitude = t[i], this.latitude = t[i + 1], this.height = t[i + 2], this;
|
184
|
+
}
|
185
|
+
toArray(t = [], i = 0) {
|
186
|
+
return t[i] = this.longitude, t[i + 1] = this.latitude, t[i + 2] = this.height, t;
|
187
|
+
}
|
188
|
+
*[Symbol.iterator]() {
|
189
|
+
yield this.longitude, yield this.latitude, yield this.height;
|
190
|
+
}
|
191
|
+
};
|
192
|
+
z(v, "MIN_LONGITUDE", -Math.PI), z(v, "MAX_LONGITUDE", Math.PI), z(v, "MIN_LATITUDE", -Math.PI / 2), z(v, "MAX_LATITUDE", Math.PI / 2);
|
193
|
+
let C = v;
|
194
|
+
export {
|
195
|
+
b as E,
|
196
|
+
C as G,
|
197
|
+
J as a
|
198
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
{
|
2
|
+
"name": "@takram/three-geospatial",
|
3
|
+
"version": "0.0.1-alpha.1",
|
4
|
+
"license": "MIT",
|
5
|
+
"type": "module",
|
6
|
+
"exports": {
|
7
|
+
".": {
|
8
|
+
"import": "./build/index.js",
|
9
|
+
"require": "./build/index.cjs",
|
10
|
+
"types": "./types/index.d.ts"
|
11
|
+
},
|
12
|
+
"./r3f": {
|
13
|
+
"import": "./build/r3f.js",
|
14
|
+
"require": "./build/r3f.cjs",
|
15
|
+
"types": "./types/r3f/index.d.ts"
|
16
|
+
}
|
17
|
+
},
|
18
|
+
"main": "./build/index.cjs",
|
19
|
+
"module": "./build/index.js",
|
20
|
+
"types": "./types/index.d.ts",
|
21
|
+
"files": [
|
22
|
+
"build",
|
23
|
+
"src",
|
24
|
+
"types",
|
25
|
+
"package.json",
|
26
|
+
"README.md"
|
27
|
+
],
|
28
|
+
"dependencies": {
|
29
|
+
"lodash-es": "^4.17.21",
|
30
|
+
"react-merge-refs": "^2.1.1",
|
31
|
+
"tiny-invariant": "^1.3.3",
|
32
|
+
"type-fest": "^4.28.0"
|
33
|
+
},
|
34
|
+
"peerDependencies": {
|
35
|
+
"@react-three/fiber": ">=8.17.10",
|
36
|
+
"react": ">=18.0",
|
37
|
+
"three": ">=0.170.0"
|
38
|
+
},
|
39
|
+
"publishConfig": {
|
40
|
+
"access": "public"
|
41
|
+
}
|
42
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import { FileLoader, Loader } from 'three'
|
2
|
+
import invariant from 'tiny-invariant'
|
3
|
+
|
4
|
+
export class ArrayBufferLoader extends Loader<ArrayBuffer> {
|
5
|
+
override load(
|
6
|
+
url: string,
|
7
|
+
onLoad: (data: ArrayBuffer) => void,
|
8
|
+
onProgress?: (event: ProgressEvent) => void,
|
9
|
+
onError?: (error: unknown) => void
|
10
|
+
): void {
|
11
|
+
const loader = new FileLoader(this.manager)
|
12
|
+
loader.setResponseType('arraybuffer')
|
13
|
+
loader.setRequestHeader(this.requestHeader)
|
14
|
+
loader.setPath(this.path)
|
15
|
+
loader.setWithCredentials(this.withCredentials)
|
16
|
+
loader.load(
|
17
|
+
url,
|
18
|
+
arrayBuffer => {
|
19
|
+
invariant(arrayBuffer instanceof ArrayBuffer)
|
20
|
+
try {
|
21
|
+
onLoad(arrayBuffer)
|
22
|
+
} catch (error) {
|
23
|
+
if (onError != null) {
|
24
|
+
onError(error)
|
25
|
+
} else {
|
26
|
+
console.error(error)
|
27
|
+
}
|
28
|
+
this.manager.itemError(url)
|
29
|
+
}
|
30
|
+
},
|
31
|
+
onProgress,
|
32
|
+
onError
|
33
|
+
)
|
34
|
+
}
|
35
|
+
}
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import {
|
2
|
+
ClampToEdgeWrapping,
|
3
|
+
Data3DTexture,
|
4
|
+
DataTexture,
|
5
|
+
FloatType,
|
6
|
+
LinearFilter,
|
7
|
+
Loader,
|
8
|
+
RGBAFormat,
|
9
|
+
type Texture,
|
10
|
+
type TypedArray
|
11
|
+
} from 'three'
|
12
|
+
import { type Class } from 'type-fest'
|
13
|
+
|
14
|
+
import {
|
15
|
+
Float32ArrayLoader,
|
16
|
+
Int16ArrayLoader,
|
17
|
+
Uint16ArrayLoader,
|
18
|
+
type TypedArrayLoader
|
19
|
+
} from './TypedArrayLoader'
|
20
|
+
import { type Callable } from './types'
|
21
|
+
|
22
|
+
export interface ImageSize {
|
23
|
+
width: number
|
24
|
+
height: number
|
25
|
+
depth?: number
|
26
|
+
}
|
27
|
+
|
28
|
+
export type DataTextureParameters = Omit<
|
29
|
+
Partial<{
|
30
|
+
[K in keyof Texture as Texture[K] extends Callable ? never : K]: Texture[K]
|
31
|
+
}>,
|
32
|
+
'image'
|
33
|
+
>
|
34
|
+
|
35
|
+
const defaultDataTextureParameter = {
|
36
|
+
format: RGBAFormat,
|
37
|
+
wrapS: ClampToEdgeWrapping,
|
38
|
+
wrapT: ClampToEdgeWrapping,
|
39
|
+
minFilter: LinearFilter,
|
40
|
+
magFilter: LinearFilter
|
41
|
+
} satisfies DataTextureParameters
|
42
|
+
|
43
|
+
export abstract class DataLoader<
|
44
|
+
T extends DataTexture | Data3DTexture,
|
45
|
+
U extends TypedArray
|
46
|
+
> extends Loader<T> {
|
47
|
+
abstract readonly Texture: Class<T>
|
48
|
+
abstract readonly TypedArrayLoader: Class<TypedArrayLoader<U>>
|
49
|
+
|
50
|
+
readonly parameters?: DataTextureParameters
|
51
|
+
|
52
|
+
override load(
|
53
|
+
url: string,
|
54
|
+
onLoad: (data: T) => void,
|
55
|
+
onProgress?: (event: ProgressEvent) => void,
|
56
|
+
onError?: (error: unknown) => void
|
57
|
+
): void {
|
58
|
+
const texture = new this.Texture()
|
59
|
+
const loader = new this.TypedArrayLoader(this.manager)
|
60
|
+
loader.setRequestHeader(this.requestHeader)
|
61
|
+
loader.setPath(this.path)
|
62
|
+
loader.setWithCredentials(this.withCredentials)
|
63
|
+
loader.load(
|
64
|
+
url,
|
65
|
+
array => {
|
66
|
+
texture.image.data = array as typeof texture.image.data
|
67
|
+
Object.assign(texture, this.parameters)
|
68
|
+
texture.needsUpdate = true
|
69
|
+
onLoad(texture)
|
70
|
+
},
|
71
|
+
onProgress,
|
72
|
+
onError
|
73
|
+
)
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
export class Int16Data2DLoader extends DataLoader<DataTexture, Int16Array> {
|
78
|
+
readonly Texture = DataTexture
|
79
|
+
readonly TypedArrayLoader = Int16ArrayLoader
|
80
|
+
readonly parameters = {
|
81
|
+
...defaultDataTextureParameter,
|
82
|
+
type: FloatType
|
83
|
+
} satisfies DataTextureParameters
|
84
|
+
}
|
85
|
+
|
86
|
+
export class Uint16Data2DLoader extends DataLoader<DataTexture, Uint16Array> {
|
87
|
+
readonly Texture = DataTexture
|
88
|
+
readonly TypedArrayLoader = Uint16ArrayLoader
|
89
|
+
readonly parameters = {
|
90
|
+
...defaultDataTextureParameter,
|
91
|
+
type: FloatType
|
92
|
+
} satisfies DataTextureParameters
|
93
|
+
}
|
94
|
+
|
95
|
+
export class Float32Data2DLoader extends DataLoader<DataTexture, Float32Array> {
|
96
|
+
readonly Texture = DataTexture
|
97
|
+
readonly TypedArrayLoader = Float32ArrayLoader
|
98
|
+
readonly parameters = {
|
99
|
+
...defaultDataTextureParameter,
|
100
|
+
type: FloatType
|
101
|
+
} satisfies DataTextureParameters
|
102
|
+
}
|
103
|
+
|
104
|
+
export class Float32Data3DLoader extends DataLoader<
|
105
|
+
Data3DTexture,
|
106
|
+
Float32Array
|
107
|
+
> {
|
108
|
+
readonly Texture = Data3DTexture
|
109
|
+
readonly TypedArrayLoader = Float32ArrayLoader
|
110
|
+
readonly parameters = {
|
111
|
+
...defaultDataTextureParameter,
|
112
|
+
type: FloatType
|
113
|
+
} satisfies DataTextureParameters
|
114
|
+
}
|
package/src/Ellipsoid.ts
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
import { Matrix4, Vector3, type Ray } from 'three'
|
2
|
+
|
3
|
+
import {
|
4
|
+
projectOnEllipsoidSurface,
|
5
|
+
type ProjectOnEllipsoidSurfaceOptions
|
6
|
+
} from './helpers/projectOnEllipsoidSurface'
|
7
|
+
|
8
|
+
const vectorScratch1 = /*#__PURE__*/ new Vector3()
|
9
|
+
const vectorScratch2 = /*#__PURE__*/ new Vector3()
|
10
|
+
const vectorScratch3 = /*#__PURE__*/ new Vector3()
|
11
|
+
|
12
|
+
export class Ellipsoid {
|
13
|
+
static readonly WGS84 = /*#__PURE__*/ new Ellipsoid(
|
14
|
+
6378137,
|
15
|
+
6378137,
|
16
|
+
6356752.3142451793
|
17
|
+
)
|
18
|
+
|
19
|
+
readonly radii: Vector3
|
20
|
+
|
21
|
+
constructor(x: number, y: number, z: number) {
|
22
|
+
this.radii = new Vector3(x, y, z)
|
23
|
+
}
|
24
|
+
|
25
|
+
get minimumRadius(): number {
|
26
|
+
return Math.min(this.radii.x, this.radii.y, this.radii.z)
|
27
|
+
}
|
28
|
+
|
29
|
+
get maximumRadius(): number {
|
30
|
+
return Math.max(this.radii.x, this.radii.y, this.radii.z)
|
31
|
+
}
|
32
|
+
|
33
|
+
reciprocalRadii(result = new Vector3()): Vector3 {
|
34
|
+
const { x, y, z } = this.radii
|
35
|
+
return result.set(1 / x, 1 / y, 1 / z)
|
36
|
+
}
|
37
|
+
|
38
|
+
reciprocalRadiiSquared(result = new Vector3()): Vector3 {
|
39
|
+
const { x, y, z } = this.radii
|
40
|
+
return result.set(1 / x ** 2, 1 / y ** 2, 1 / z ** 2)
|
41
|
+
}
|
42
|
+
|
43
|
+
projectOnSurface(
|
44
|
+
position: Vector3,
|
45
|
+
result = new Vector3(),
|
46
|
+
options?: ProjectOnEllipsoidSurfaceOptions
|
47
|
+
): Vector3 | undefined {
|
48
|
+
return projectOnEllipsoidSurface(
|
49
|
+
position,
|
50
|
+
this.reciprocalRadiiSquared(),
|
51
|
+
result,
|
52
|
+
options
|
53
|
+
)
|
54
|
+
}
|
55
|
+
|
56
|
+
getSurfaceNormal(position: Vector3, result = new Vector3()): Vector3 {
|
57
|
+
return result
|
58
|
+
.multiplyVectors(this.reciprocalRadiiSquared(vectorScratch1), position)
|
59
|
+
.normalize()
|
60
|
+
}
|
61
|
+
|
62
|
+
getEastNorthUpVectors(
|
63
|
+
position: Vector3,
|
64
|
+
east = new Vector3(),
|
65
|
+
north = new Vector3(),
|
66
|
+
up = new Vector3()
|
67
|
+
): void {
|
68
|
+
this.getSurfaceNormal(position, up)
|
69
|
+
east.set(-position.y, position.x, 0).normalize()
|
70
|
+
north.crossVectors(up, east).normalize()
|
71
|
+
}
|
72
|
+
|
73
|
+
getEastNorthUpFrame(position: Vector3, result = new Matrix4()): Matrix4 {
|
74
|
+
const east = vectorScratch1
|
75
|
+
const north = vectorScratch2
|
76
|
+
const up = vectorScratch3
|
77
|
+
this.getEastNorthUpVectors(position, east, north, up)
|
78
|
+
return result.makeBasis(east, north, up).setPosition(position)
|
79
|
+
}
|
80
|
+
|
81
|
+
getIntersection(ray: Ray, result = new Vector3()): Vector3 | undefined {
|
82
|
+
const reciprocalRadii = this.reciprocalRadii(vectorScratch1)
|
83
|
+
const p = vectorScratch2.copy(reciprocalRadii).multiply(ray.origin)
|
84
|
+
const d = vectorScratch3.copy(reciprocalRadii).multiply(ray.direction)
|
85
|
+
const p2 = p.lengthSq()
|
86
|
+
const d2 = d.lengthSq()
|
87
|
+
const pd = p.dot(d)
|
88
|
+
const discriminant = pd ** 2 - d2 * (p2 - 1)
|
89
|
+
if (p2 === 1) {
|
90
|
+
return result.copy(ray.origin)
|
91
|
+
}
|
92
|
+
if (p2 > 1) {
|
93
|
+
if (pd >= 0 || discriminant < 0) {
|
94
|
+
return // No intersection
|
95
|
+
}
|
96
|
+
const Q = Math.sqrt(discriminant)
|
97
|
+
const t1 = (-pd - Q) / d2
|
98
|
+
const t2 = (-pd + Q) / d2
|
99
|
+
return ray.at(Math.min(t1, t2), result)
|
100
|
+
}
|
101
|
+
if (p2 < 1) {
|
102
|
+
const discriminant = pd ** 2 - d2 * (p2 - 1)
|
103
|
+
const Q = Math.sqrt(discriminant)
|
104
|
+
const t = (-pd + Q) / d2
|
105
|
+
return ray.at(t, result) // Backface of the ellipsoid
|
106
|
+
}
|
107
|
+
if (pd < 0) {
|
108
|
+
return ray.at(-pd / d2, result) // Backface of the ellipsoid
|
109
|
+
}
|
110
|
+
// No intersection
|
111
|
+
}
|
112
|
+
|
113
|
+
getOsculatingSphereCenter(
|
114
|
+
surfacePosition: Vector3,
|
115
|
+
radius: number,
|
116
|
+
result = new Vector3()
|
117
|
+
): Vector3 {
|
118
|
+
const xySquared = this.radii.x ** 2
|
119
|
+
const normal = vectorScratch1
|
120
|
+
.set(
|
121
|
+
surfacePosition.x / xySquared,
|
122
|
+
surfacePosition.y / xySquared,
|
123
|
+
surfacePosition.z / this.radii.z ** 2
|
124
|
+
)
|
125
|
+
.normalize()
|
126
|
+
return result.copy(normal.multiplyScalar(-radius).add(surfacePosition))
|
127
|
+
}
|
128
|
+
}
|
@@ -0,0 +1,107 @@
|
|
1
|
+
import { BufferAttribute, BufferGeometry, Vector3 } from 'three'
|
2
|
+
|
3
|
+
export interface EllipsoidGeometryParameters {
|
4
|
+
radii: Vector3
|
5
|
+
longitudeSegments?: number
|
6
|
+
latitudeSegments?: number
|
7
|
+
}
|
8
|
+
|
9
|
+
export class EllipsoidGeometry extends BufferGeometry {
|
10
|
+
readonly type = 'EllipsoidGeometry'
|
11
|
+
|
12
|
+
parameters: EllipsoidGeometryParameters
|
13
|
+
|
14
|
+
constructor(
|
15
|
+
radii = new Vector3(1, 1, 1),
|
16
|
+
longitudeSegments = 32,
|
17
|
+
latitudeSegments = 16
|
18
|
+
) {
|
19
|
+
super()
|
20
|
+
this.parameters = {
|
21
|
+
radii,
|
22
|
+
longitudeSegments,
|
23
|
+
latitudeSegments
|
24
|
+
}
|
25
|
+
|
26
|
+
longitudeSegments = Math.max(3, Math.floor(longitudeSegments))
|
27
|
+
latitudeSegments = Math.max(2, Math.floor(latitudeSegments))
|
28
|
+
|
29
|
+
const elementCount = (longitudeSegments + 1) * (latitudeSegments + 1)
|
30
|
+
const vertex = new Vector3()
|
31
|
+
const normal = new Vector3()
|
32
|
+
const vertices = new Float32Array(elementCount * 3)
|
33
|
+
const normals = new Float32Array(elementCount * 3)
|
34
|
+
const uvs = new Float32Array(elementCount * 2)
|
35
|
+
const grid: number[][] = []
|
36
|
+
const indices: number[] = []
|
37
|
+
|
38
|
+
// Vertices, normals and UVs
|
39
|
+
for (
|
40
|
+
let y = 0, vertexIndex = 0, uvIndex = 0, rowIndex = 0;
|
41
|
+
y <= latitudeSegments;
|
42
|
+
++y
|
43
|
+
) {
|
44
|
+
const rowIndices = []
|
45
|
+
const v = y / latitudeSegments
|
46
|
+
const phi = v * Math.PI
|
47
|
+
|
48
|
+
// Special case for the poles
|
49
|
+
let uOffset = 0
|
50
|
+
if (y === 0) {
|
51
|
+
uOffset = 0.5 / longitudeSegments
|
52
|
+
} else if (y === latitudeSegments) {
|
53
|
+
uOffset = -0.5 / longitudeSegments
|
54
|
+
}
|
55
|
+
|
56
|
+
for (
|
57
|
+
let x = 0;
|
58
|
+
x <= longitudeSegments;
|
59
|
+
++x, vertexIndex += 3, uvIndex += 2, ++rowIndex
|
60
|
+
) {
|
61
|
+
const u = x / longitudeSegments
|
62
|
+
const theta = u * Math.PI * 2
|
63
|
+
vertex.x = radii.x * Math.cos(theta) * Math.sin(phi)
|
64
|
+
vertex.y = radii.y * Math.sin(theta) * Math.sin(phi)
|
65
|
+
vertex.z = radii.z * Math.cos(phi)
|
66
|
+
vertices[vertexIndex] = vertex.x
|
67
|
+
vertices[vertexIndex + 1] = vertex.y
|
68
|
+
vertices[vertexIndex + 2] = vertex.z
|
69
|
+
normal.copy(vertex).normalize()
|
70
|
+
normals[vertexIndex] = normal.x
|
71
|
+
normals[vertexIndex + 1] = normal.y
|
72
|
+
normals[vertexIndex + 2] = normal.z
|
73
|
+
uvs[uvIndex] = u + uOffset
|
74
|
+
uvs[uvIndex + 1] = 1 - v
|
75
|
+
rowIndices.push(rowIndex)
|
76
|
+
}
|
77
|
+
grid.push(rowIndices)
|
78
|
+
}
|
79
|
+
|
80
|
+
// Indices
|
81
|
+
for (let y = 0; y < latitudeSegments; ++y) {
|
82
|
+
for (let x = 0; x < longitudeSegments; ++x) {
|
83
|
+
const a = grid[y][x + 1]
|
84
|
+
const b = grid[y][x]
|
85
|
+
const c = grid[y + 1][x]
|
86
|
+
const d = grid[y + 1][x + 1]
|
87
|
+
if (y !== 0) {
|
88
|
+
indices.push(a, b, d)
|
89
|
+
}
|
90
|
+
if (y !== latitudeSegments - 1) {
|
91
|
+
indices.push(b, c, d)
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
this.setIndex(indices)
|
97
|
+
this.setAttribute('position', new BufferAttribute(vertices, 3))
|
98
|
+
this.setAttribute('normal', new BufferAttribute(normals, 3))
|
99
|
+
this.setAttribute('uv', new BufferAttribute(uvs, 2))
|
100
|
+
}
|
101
|
+
|
102
|
+
copy(source: EllipsoidGeometry): this {
|
103
|
+
super.copy(source)
|
104
|
+
this.parameters = { ...source.parameters }
|
105
|
+
return this
|
106
|
+
}
|
107
|
+
}
|