Skip to content

Commit c525f07

Browse files
authored
Fix GeoJSON point aspect ratio (#1325)
1 parent b79a89e commit c525f07

File tree

1 file changed

+47
-9
lines changed

1 file changed

+47
-9
lines changed

src/three/plugins/images/sources/GeoJSONImageSource.js

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
1-
import { CanvasTexture, MathUtils } from 'three';
1+
import { CanvasTexture, MathUtils, Vector3 } from 'three';
22
import { TiledImageSource } from './TiledImageSource.js';
33
import { 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+
1030
export 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

Comments
 (0)