1- import { CanvasTexture , MathUtils } from 'three' ;
1+ import { CanvasTexture , MathUtils , Vector3 } from 'three' ;
22import { TiledImageSource } from './TiledImageSource.js' ;
33import { ProjectionScheme } from '../utils/ProjectionScheme.js' ;
4+ import { WGS84_ELLIPSOID } from '3d-tiles-renderer/three' ;
45
56// TODO: Add support for limited bounds
67// TODO: Add support for padding of tiles to avoid clipping "wide" elements
7- // TODO: Scale points ellipse radii based on localized lat / lon distortion to preserve a circular appearance
88// TODO: Need to clip / fix geojson shapes across the 180 degree boundary
99// TODO: Add support for easy regeneration when colors / styles / geojson change
10+ // TODO: Consider option to support world-space thickness definitions. Eg world-space point size or line thickness in meters.
11+
12+ // function for calculating the the change in arc length at a given cartographic point
13+ // in order to preserve a circular look when drawing points
14+ const _v0 = /* @__PURE__ */ new Vector3 ( ) ;
15+ const _v1 = /* @__PURE__ */ new Vector3 ( ) ;
16+ function calculateArcRatioAtPoint ( ellipsoid , lat , lon ) {
17+
18+ const DELTA = 0.01 ;
19+ ellipsoid . getCartographicToPosition ( lat , lon , 0 , _v0 ) ;
20+ ellipsoid . getCartographicToPosition ( lat + DELTA , lon , 0 , _v1 ) ;
21+
22+ const latDelta = _v0 . distanceTo ( _v1 ) ;
23+ ellipsoid . getCartographicToPosition ( lat , lon + DELTA , 0 , _v1 ) ;
24+
25+ const lonDelta = _v0 . distanceTo ( _v1 ) ;
26+ return lonDelta / latDelta ;
27+
28+ }
29+
1030export class GeoJSONImageSource extends TiledImageSource {
1131
1232 constructor ( {
@@ -213,6 +233,7 @@ export class GeoJSONImageSource extends TiledImageSource {
213233
214234 }
215235
236+ const [ minLonDeg , minLatDeg , maxLonDeg , maxLatDeg ] = tileBoundsDeg ;
216237 const strokeStyle = properties . strokeStyle || this . strokeStyle ;
217238 const fillStyle = properties . fillStyle || this . fillStyle ;
218239 const pointRadius = properties . pointRadius || this . pointRadius ;
@@ -227,11 +248,9 @@ export class GeoJSONImageSource extends TiledImageSource {
227248 const arr = new Array ( 2 ) ;
228249 const projectPoint = ( lon , lat , target = arr ) => {
229250
230- const [ minX , minY , maxX , maxY ] = tileBoundsDeg ;
231-
232251 // canvas y origin is top, projection y increases north -> flip
233- const x = MathUtils . mapLinear ( lon , minX , maxX , 0 , width ) ;
234- const y = height - MathUtils . mapLinear ( lat , minY , maxY , 0 , height ) ;
252+ const x = MathUtils . mapLinear ( lon , minLonDeg , maxLonDeg , 0 , width ) ;
253+ const y = height - MathUtils . mapLinear ( lat , minLatDeg , maxLatDeg , 0 , height ) ;
235254
236255 // round to integer to gain performance
237256 // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas#avoid_floating-point_coordinates_and_use_integers_instead
@@ -241,24 +260,43 @@ export class GeoJSONImageSource extends TiledImageSource {
241260
242261 } ;
243262
244- const type = geometry . type ;
263+ const calculateAspectRatio = ( lon , lat ) => {
245264
265+ // calculates the aspect ratio with which to draw points
266+ const latRad = lat * MathUtils . DEG2RAD ;
267+ const lonRad = lon * MathUtils . DEG2RAD ;
268+ const pxLat = ( maxLatDeg - minLatDeg ) / height ;
269+ const pxLon = ( maxLonDeg - minLonDeg ) / width ;
270+ const pixelRatio = pxLon / pxLat ;
271+
272+ // TODO: this should use the ellipsoid defined on the relevant tiles renderer
273+ return pixelRatio * calculateArcRatioAtPoint ( WGS84_ELLIPSOID , latRad , lonRad ) ;
274+
275+ } ;
276+
277+ const type = geometry . type ;
246278 if ( type === 'Point' ) {
247279
248280 const [ lon , lat ] = geometry . coordinates ;
249281 const [ px , py ] = projectPoint ( lon , lat ) ;
282+ const drawRatio = calculateAspectRatio ( lon , lat ) ;
283+
250284 ctx . beginPath ( ) ;
251- ctx . ellipse ( px , py , pointRadius , pointRadius , 0 , 0 , Math . PI * 2 ) ;
285+ ctx . ellipse ( px , py , pointRadius / drawRatio , pointRadius , 0 , 0 , Math . PI * 2 ) ;
252286 ctx . fill ( ) ;
287+ ctx . stroke ( ) ;
253288
254289 } else if ( type === 'MultiPoint' ) {
255290
256291 geometry . coordinates . forEach ( ( [ lon , lat ] ) => {
257292
258293 const [ px , py ] = projectPoint ( lon , lat ) ;
294+ const drawRatio = calculateAspectRatio ( lon , lat ) ;
295+
259296 ctx . beginPath ( ) ;
260- ctx . ellipse ( px , py , pointRadius , pointRadius , 0 , 0 , Math . PI * 2 ) ;
297+ ctx . ellipse ( px , py , pointRadius / drawRatio , pointRadius , 0 , 0 , Math . PI * 2 ) ;
261298 ctx . fill ( ) ;
299+ ctx . stroke ( ) ;
262300
263301 } ) ;
264302
0 commit comments