Skip to content

Commit f4c2ff6

Browse files
committed
fix for not drawing vectors with coordinates outside of the web mercator bounds. bump version to 4.2.1
1 parent 7719874 commit f4c2ff6

File tree

4 files changed

+85
-36
lines changed

4 files changed

+85
-36
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ The GeoPackage JavaScript library currently provides the ability to read GeoPack
3838

3939
### Changelog
4040

41+
##### 4.2.1
42+
43+
- Fix for drawing geometries outside of the 3857 bounds
44+
4145
##### 4.2.0
4246

4347
- Support for drawing vector data into EPSG:4326 tiles

lib/projection/projection.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,8 @@ export class Projection {
9090
static convertersMatch(converterA: any, converterB: any): boolean {
9191
return isEqual(converterA.oProj, converterB.oProj);
9292
}
93+
94+
static getConverterFromConverters(from: any, to?: any): proj4.Converter {
95+
return proj4(from, to);
96+
}
9397
}

lib/tiles/features/index.ts

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
22
// @ts-ignore
3-
import reproject from 'reproject';
43
import PolyToLine from '@turf/polygon-to-line';
54
import booleanClockwise from '@turf/boolean-clockwise';
65
import simplify from 'simplify-js';
@@ -532,16 +531,31 @@ export class FeatureTiles {
532531
}
533532

534533
/**
535-
* Transform geojson to web mercator if it is in another projection.
536-
* @param geoJson
537-
* @param tileProjection
538-
*/
539-
transformGeometry(geoJson: any, tileProjection: string): any {
540-
const targetProjection = Projection.getConverter(tileProjection);
541-
if (this.projection !== targetProjection) {
542-
return reproject.reproject(geoJson, this.projection, targetProjection);
534+
* Handles the generation of a function for transforming coordinates from the source projection into the target tile's
535+
* projection. These coordinates are then converted into pixel coordinates.
536+
* @param targetProjection
537+
*/
538+
getTransformFunction(targetProjection): Function {
539+
const projection = Projection.getConverter(targetProjection);
540+
if (Projection.convertersMatch(projection, this.projection)) {
541+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
542+
return coordinate => coordinate;
543+
} else if (Projection.isWebMercator(projection) && Projection.isWGS84(this.projection)) {
544+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
545+
return coordinate => {
546+
return Projection.getConverterFromConverters(this.projection, targetProjection).forward([
547+
Math.max(
548+
ProjectionConstants.WEB_MERCATOR_MIN_LON_RANGE,
549+
Math.min(ProjectionConstants.WEB_MERCATOR_MAX_LON_RANGE, coordinate[0]),
550+
),
551+
Math.max(
552+
ProjectionConstants.WEB_MERCATOR_MIN_LAT_RANGE,
553+
Math.min(ProjectionConstants.WEB_MERCATOR_MAX_LAT_RANGE, coordinate[1]),
554+
),
555+
]);
556+
};
543557
} else {
544-
return geoJson;
558+
return Projection.getConverterFromConverters(this.projection, targetProjection).forward;
545559
}
546560
}
547561

@@ -574,6 +588,7 @@ export class FeatureTiles {
574588
const featureCount = this.featureDao.countInBoundingBox(expandedBoundingBox, tileProjection);
575589
if (featureCount > 0) {
576590
if (this.maxFeaturesPerTile == null || featureCount <= this.maxFeaturesPerTile) {
591+
const transform = this.getTransformFunction(tileProjection);
577592
const iterator = this.featureDao.fastQueryBoundingBox(expandedBoundingBox, tileProjection);
578593
for (const featureRow of iterator) {
579594
if (featureRow.geometry != null) {
@@ -587,7 +602,7 @@ export class FeatureTiles {
587602
}
588603
const style = this.getFeatureStyle(featureRow);
589604
try {
590-
await this.drawGeometry(this.transformGeometry(geojson, tileProjection), context, boundingBox, style);
605+
await this.drawGeometry(geojson, context, boundingBox, style, transform);
591606
} catch (e) {
592607
console.error(
593608
'Failed to draw feature in tile. Id: ' + featureRow.id + ', Table: ' + this.featureDao.table_name,
@@ -633,6 +648,7 @@ export class FeatureTiles {
633648
featureDao.table.getIdColumn().getName(),
634649
featureDao.table.getGeometryColumn().getName(),
635650
]);
651+
const transform = this.getTransformFunction(tileProjection);
636652
for (const row of each) {
637653
const fr = featureDao.getRow(row);
638654
if (fr.geometry != null) {
@@ -647,7 +663,7 @@ export class FeatureTiles {
647663
if (gj != null) {
648664
const style = this.getFeatureStyle(fr);
649665
try {
650-
await this.drawGeometry(this.transformGeometry(gj, tileProjection), context, boundingBox, style);
666+
await this.drawGeometry(gj, context, boundingBox, style, transform);
651667
} catch (e) {
652668
console.error('Failed to draw feature in tile. Id: ' + fr.id + ', Table: ' + this.featureDao.table_name);
653669
}
@@ -666,14 +682,22 @@ export class FeatureTiles {
666682
* @param context
667683
* @param boundingBox
668684
* @param featureStyle
685+
* @param transform
669686
*/
670-
async drawPoint(geoJson: any, context: any, boundingBox: BoundingBox, featureStyle: FeatureStyle): Promise<void> {
687+
async drawPoint(
688+
geoJson: any,
689+
context: any,
690+
boundingBox: BoundingBox,
691+
featureStyle: FeatureStyle,
692+
transform: Function,
693+
): Promise<void> {
671694
let width: number;
672695
let height: number;
673696
let iconX: number;
674697
let iconY: number;
675-
const x = TileBoundingBoxUtils.getXPixel(this.tileWidth, boundingBox, geoJson.coordinates[0]);
676-
const y = TileBoundingBoxUtils.getYPixel(this.tileHeight, boundingBox, geoJson.coordinates[1]);
698+
const coordinate = transform(geoJson.coordinates);
699+
const x = TileBoundingBoxUtils.getXPixel(this.tileWidth, boundingBox, coordinate[0]);
700+
const y = TileBoundingBoxUtils.getYPixel(this.tileHeight, boundingBox, coordinate[1]);
677701
if (featureStyle != null && featureStyle.useIcon()) {
678702
const iconRow = featureStyle.icon;
679703
const image = await this.iconCache.createIcon(iconRow);
@@ -757,14 +781,18 @@ export class FeatureTiles {
757781
* @param context
758782
* @param boundingBox
759783
* @param isPolygon if this was a polygon
760-
*/
761-
getPath(lineString: any, context: any, boundingBox: BoundingBox, isPolygon = false): void {
762-
lineString.coordinates = lineString.coordinates.map(coordinate => [
763-
TileBoundingBoxUtils.getXPixel(this.tileWidth, boundingBox, coordinate[0]),
764-
TileBoundingBoxUtils.getYPixel(this.tileHeight, boundingBox, coordinate[1]),
765-
]);
784+
* @param transform
785+
*/
786+
getPath(lineString: any, context: any, boundingBox: BoundingBox, isPolygon = false, transform: Function): void {
787+
lineString.coordinates = lineString.coordinates.map(coordinate => {
788+
const transformedCoordinate = transform(coordinate);
789+
return [
790+
TileBoundingBoxUtils.getXPixel(this.tileWidth, boundingBox, transformedCoordinate[0]),
791+
TileBoundingBoxUtils.getYPixel(this.tileHeight, boundingBox, transformedCoordinate[1]),
792+
];
793+
});
766794
const simplifiedLineString = this.simplifyGeometries ? this.simplifyPoints(lineString, isPolygon) : lineString;
767-
if (simplifiedLineString != null && simplifiedLineString.coordinates.length > 0) {
795+
if (simplifiedLineString.coordinates.length > 0) {
768796
context.moveTo(simplifiedLineString.coordinates[0][0], simplifiedLineString.coordinates[0][1]);
769797
for (let i = 1; i < simplifiedLineString.coordinates.length; i++) {
770798
context.lineTo(simplifiedLineString.coordinates[i][0], simplifiedLineString.coordinates[i][1]);
@@ -777,14 +805,21 @@ export class FeatureTiles {
777805
* @param context
778806
* @param featureStyle
779807
* @param boundingBox
808+
* @param transform
780809
*/
781-
drawLine(geoJson: any, context: any, featureStyle: FeatureStyle, boundingBox: BoundingBox): void {
810+
drawLine(
811+
geoJson: any,
812+
context: any,
813+
featureStyle: FeatureStyle,
814+
boundingBox: BoundingBox,
815+
transform: Function,
816+
): void {
782817
context.save();
783818
context.beginPath();
784819
const paint = this.getLinePaint(featureStyle);
785820
context.strokeStyle = paint.colorRGBA;
786821
context.lineWidth = paint.strokeWidth;
787-
this.getPath(geoJson, context, boundingBox);
822+
this.getPath(geoJson, context, boundingBox, false, transform);
788823
context.stroke();
789824
context.closePath();
790825
context.restore();
@@ -796,6 +831,7 @@ export class FeatureTiles {
796831
* @param context
797832
* @param featureStyle
798833
* @param boundingBox
834+
* @param transform
799835
* @param fill
800836
*/
801837
drawPolygon(
@@ -804,6 +840,7 @@ export class FeatureTiles {
804840
context: any,
805841
featureStyle: FeatureStyle,
806842
boundingBox: BoundingBox,
843+
transform: Function,
807844
fill = true,
808845
): void {
809846
// get paint
@@ -812,13 +849,13 @@ export class FeatureTiles {
812849
if (!booleanClockwise(externalRing.coordinates)) {
813850
externalRing.coordinates = externalRing.coordinates.reverse();
814851
}
815-
this.getPath(externalRing, context, boundingBox, true);
852+
this.getPath(externalRing, context, boundingBox, true, transform);
816853
context.closePath();
817854
for (let i = 0; i < internalRings.length; i++) {
818855
if (booleanClockwise(internalRings[i].coordinates)) {
819856
internalRings[i].coordinates = internalRings[i].coordinates.reverse();
820857
}
821-
this.getPath(internalRings[i], context, boundingBox, true);
858+
this.getPath(internalRings[i], context, boundingBox, true, transform);
822859
context.closePath();
823860
}
824861
const fillPaint = this.getPolygonFillPaint(featureStyle);
@@ -834,28 +871,29 @@ export class FeatureTiles {
834871
}
835872
/**
836873
* Add a feature to the batch
837-
* @param simplifyTolerance
838874
* @param geoJson
839875
* @param context
840876
* @param boundingBox
841877
* @param featureStyle
878+
* @param transform
842879
*/
843880
async drawGeometry(
844881
geoJson: Geometry,
845882
context: any,
846883
boundingBox: BoundingBox,
847884
featureStyle: FeatureStyle,
885+
transform: Function,
848886
): Promise<void> {
849887
let i;
850888
if (geoJson.type === 'Point') {
851-
await this.drawPoint(geoJson, context, boundingBox, featureStyle);
889+
await this.drawPoint(geoJson, context, boundingBox, featureStyle, transform);
852890
} else if (geoJson.type === 'LineString') {
853-
this.drawLine(geoJson, context, featureStyle, boundingBox);
891+
this.drawLine(geoJson, context, featureStyle, boundingBox, transform);
854892
} else if (geoJson.type === 'Polygon') {
855893
const converted = PolyToLine(geoJson);
856894
if (converted.type === 'Feature') {
857895
if (converted.geometry.type === 'LineString') {
858-
this.drawPolygon(converted.geometry, [], context, featureStyle, boundingBox);
896+
this.drawPolygon(converted.geometry, [], context, featureStyle, boundingBox, transform);
859897
} else if (converted.geometry.type === 'MultiLineString') {
860898
// internal rings
861899
// draw internal rings without fill
@@ -866,12 +904,12 @@ export class FeatureTiles {
866904
coordinates: coords,
867905
};
868906
});
869-
this.drawPolygon(externalRing, internalRings, context, featureStyle, boundingBox);
907+
this.drawPolygon(externalRing, internalRings, context, featureStyle, boundingBox, transform);
870908
}
871909
} else {
872910
converted.features.forEach(feature => {
873911
if (feature.geometry.type === 'LineString') {
874-
this.drawPolygon(feature.geometry, [], context, featureStyle, boundingBox);
912+
this.drawPolygon(feature.geometry, [], context, featureStyle, boundingBox, transform);
875913
} else if (feature.geometry.type === 'MultiLineString') {
876914
const externalRing = { type: 'LineString', coordinates: feature.geometry.coordinates[0] };
877915
const internalRings = feature.geometry.coordinates.slice(1).map(coords => {
@@ -880,7 +918,7 @@ export class FeatureTiles {
880918
coordinates: coords,
881919
};
882920
});
883-
this.drawPolygon(externalRing, internalRings, context, featureStyle, boundingBox);
921+
this.drawPolygon(externalRing, internalRings, context, featureStyle, boundingBox, transform);
884922
}
885923
});
886924
}
@@ -894,6 +932,7 @@ export class FeatureTiles {
894932
context,
895933
boundingBox,
896934
featureStyle,
935+
transform,
897936
);
898937
}
899938
} else if (geoJson.type === 'MultiLineString') {
@@ -906,6 +945,7 @@ export class FeatureTiles {
906945
context,
907946
featureStyle,
908947
boundingBox,
948+
transform,
909949
);
910950
}
911951
} else if (geoJson.type === 'MultiPolygon') {
@@ -918,11 +958,12 @@ export class FeatureTiles {
918958
context,
919959
boundingBox,
920960
featureStyle,
961+
transform,
921962
);
922963
}
923964
} else if (geoJson.type === 'GeometryCollection') {
924965
for (i = 0; i < geoJson.geometries.length; i++) {
925-
await this.drawGeometry(geoJson.geometries[i], context, boundingBox, featureStyle);
966+
await this.drawGeometry(geoJson.geometries[i], context, boundingBox, featureStyle, transform);
926967
}
927968
}
928969
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ngageoint/geopackage",
3-
"version": "4.2.0",
3+
"version": "4.2.1",
44
"description": "GeoPackage JavaScript Library",
55
"keywords": [
66
"NGA",
@@ -41,8 +41,8 @@
4141
"file-type": "12.4.0",
4242
"image-size": "0.8.3",
4343
"lodash": "4.17.21",
44-
"proj4": "2.7.2",
4544
"reproject": "1.2.5",
45+
"proj4": "2.8.0",
4646
"rtree-sql.js": "1.7.0",
4747
"simplify-js": "1.2.4",
4848
"webworkify": "1.5.0",

0 commit comments

Comments
 (0)