Skip to content

Commit

Permalink
updates to support 4326 tile sets and drawing tiles in 4326
Browse files Browse the repository at this point in the history
  • Loading branch information
caldwellc committed Jun 3, 2022
1 parent 2649e95 commit 7719874
Show file tree
Hide file tree
Showing 59 changed files with 652 additions and 159 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -61,5 +61,6 @@ tmp
bundle
test/bundle
test/sql-wasm.wasm
docs/coverage

.idea
5 changes: 5 additions & 0 deletions README.md
Expand Up @@ -38,6 +38,11 @@ The GeoPackage JavaScript library currently provides the ability to read GeoPack

### Changelog

##### 4.2.0

- Support for drawing vector data into EPSG:4326 tiles
- Added createStandardWGS84TileTable

##### 4.1.0

- Typescript updates
Expand Down
40 changes: 23 additions & 17 deletions lib/boundingBox.ts
Expand Up @@ -145,7 +145,6 @@ export class BoundingBox {
);
}


/**
* Project the bounding box into a new projection
*
Expand All @@ -154,12 +153,14 @@ export class BoundingBox {
* @return {BoundingBox}
*/
projectBoundingBox(from?: string | proj4.Converter, to?: string | proj4.Converter): BoundingBox {
let minLatitude = this.minLatitude
let maxLatitude = this.maxLatitude
let minLongitude = this.minLongitude
let maxLongitude = this.maxLongitude
let minLatitude = this.minLatitude;
let maxLatitude = this.maxLatitude;
let minLongitude = this.minLongitude;
let maxLongitude = this.maxLongitude;

if (from && from !== 'undefined' && to && to !== 'undefined') {
if (!Projection.isConverter(to) && to.toUpperCase() === ProjectionConstants.EPSG_3857 && !Projection.isConverter(from) && from.toUpperCase() === ProjectionConstants.EPSG_4326) {
// if we are going from 4326 to 3857, we first need to trim to the maximum for 3857
if (Projection.isWebMercator(to) && Projection.isWGS84(from)) {
maxLatitude = Math.min(maxLatitude, ProjectionConstants.WEB_MERCATOR_MAX_LAT_RANGE);
minLatitude = Math.max(minLatitude, ProjectionConstants.WEB_MERCATOR_MIN_LAT_RANGE);
maxLongitude = Math.min(maxLongitude, ProjectionConstants.WEB_MERCATOR_MAX_LON_RANGE);
Expand All @@ -179,17 +180,22 @@ export class BoundingBox {
fromConverter = Projection.getConverter(from);
}

const sw = toConverter.forward(fromConverter.inverse([minLongitude, minLatitude]));
const ne = toConverter.forward(fromConverter.inverse([maxLongitude, maxLatitude]));
const se = toConverter.forward(fromConverter.inverse([maxLongitude, minLatitude]));
const nw = toConverter.forward(fromConverter.inverse([minLongitude, maxLatitude]));

return new BoundingBox(
Math.min(sw[0], nw[0]),
Math.max(ne[0], se[0]),
Math.min(sw[1], se[1]),
Math.max(ne[1], se[1]),
);
// no need to convert if converters are the same
if (Projection.convertersMatch(toConverter, fromConverter)) {
return new BoundingBox(minLongitude, maxLongitude, minLatitude, maxLatitude);
} else {
const sw = toConverter.forward(fromConverter.inverse([minLongitude, minLatitude]));
const ne = toConverter.forward(fromConverter.inverse([maxLongitude, maxLatitude]));
const se = toConverter.forward(fromConverter.inverse([maxLongitude, minLatitude]));
const nw = toConverter.forward(fromConverter.inverse([minLongitude, maxLatitude]));

return new BoundingBox(
Math.min(sw[0], nw[0]),
Math.max(ne[0], se[0]),
Math.min(sw[1], se[1]),
Math.max(ne[1], se[1]),
);
}
}
return this;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/features/user/featureDao.ts
Expand Up @@ -126,7 +126,7 @@ export class FeatureDao<T extends FeatureRow> extends UserDao<FeatureRow> {
* @returns {Number}
*/
countWebMercatorBoundingBox(boundingBox: BoundingBox): number {
return this.featureTableIndex.countWithBoundingBox(boundingBox, ProjectionConstants.EPSG_3857);
return this.countInBoundingBox(boundingBox, ProjectionConstants.EPSG_3857);
}
/**
* Query for count in bounding box
Expand Down
140 changes: 140 additions & 0 deletions lib/geoPackage.ts
Expand Up @@ -1124,7 +1124,110 @@ export class GeoPackage {
this.tileMatrixSetDao.create(tileMatrixSet);
return tileMatrixSet;
}
/**
* Create the [tables and rows](https://www.geopackage.org/spec121/index.html#tiles)
* necessary to store tiles according to the ubiquitous [XYZ web/slippy-map tiles](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames) scheme.
* The extent for the [contents table]{@link module:core/contents~Contents} row,
* `contentsBoundingBox`, is [informational only](https://www.geopackage.org/spec121/index.html#gpkg_contents_cols),
* and need not match the [tile matrix set]{@link module:tiles/matrixset~TileMatrixSet}
* extent, `tileMatrixSetBoundingBox`, which should be the precise bounding box
* used to calculate the tile row and column coordinates of all tiles in the
* tile set. The two SRS ID parameters, `contentsSrsId` and `tileMatrixSetSrsId`,
* must match, however. See {@link module:tiles/matrixset~TileMatrixSet} for
* more information about how GeoPackage consumers use the bouding boxes for a
* tile set.
*
* @param {string} tableName the name of the table that will store the tiles
* @param {BoundingBox} contentsBoundingBox the bounds stored in the [`gpkg_contents`]{@link module:core/contents~Contents} table row for the tile matrix set
* @param {SRSRef} contentsSrsId the ID of a [spatial reference system]{@link module:core/srs~SpatialReferenceSystem}; must match `tileMatrixSetSrsId`
* @param {BoundingBox} tileMatrixSetBoundingBox the bounds stored in the [`gpkg_tile_matrix_set`]{@link module:tiles/matrixset~TileMatrixSet} table row
* @param {SRSRef} tileMatrixSetSrsId the ID of a [spatial reference system]{@link module:core/srs~SpatialReferenceSystem}
* for the [tile matrix set](https://www.geopackage.org/spec121/index.html#_tile_matrix_set) table; must match `contentsSrsId`
* @param {number} minZoom the zoom level of the lowest resolution [tile matrix]{@link module:tiles/matrix~TileMatrix} in the tile matrix set
* @param {number} maxZoom the zoom level of the highest resolution [tile matrix]{@link module:tiles/matrix~TileMatrix} in the tile matrix set
* @param tileSize the width and height in pixels of the tile images; defaults to 256
* @returns {TileMatrixSet} the created {@link module:tiles/matrixset~TileMatrixSet} object, or rejects with an `Error`
*
*/
createStandardWGS84TileTable(
tableName: string,
contentsBoundingBox: BoundingBox,
contentsSrsId: number,
tileMatrixSetBoundingBox: BoundingBox,
tileMatrixSetSrsId: number,
minZoom: number,
maxZoom: number,
tileSize = 256,
): TileMatrixSet {
let wgs84 = this.spatialReferenceSystemDao.getByOrganizationAndCoordSysId(
ProjectionConstants.EPSG,
ProjectionConstants.EPSG_CODE_4326,
);
if (!wgs84) {
this.spatialReferenceSystemDao.createWebMercator();
wgs84 = this.spatialReferenceSystemDao.getByOrganizationAndCoordSysId(
ProjectionConstants.EPSG,
ProjectionConstants.EPSG_CODE_4326,
);
}
const wgs84SrsId = wgs84.srs_id;

let srs = this.spatialReferenceSystemDao.getBySrsId(contentsSrsId);
if (!srs) {
throw new Error('Spatial reference system (' + contentsSrsId + ') is not defined.');
}
srs = this.spatialReferenceSystemDao.getBySrsId(tileMatrixSetSrsId);
if (!srs) {
throw new Error('Spatial reference system (' + tileMatrixSetSrsId + ') is not defined.');
}

if (contentsSrsId !== wgs84SrsId) {
const srsDao = new SpatialReferenceSystemDao(this);
const from = srsDao.getBySrsId(contentsSrsId).projection;
contentsBoundingBox = contentsBoundingBox.projectBoundingBox(from, ProjectionConstants.EPSG_4326);
}
if (tileMatrixSetSrsId !== wgs84SrsId) {
const srsDao = new SpatialReferenceSystemDao(this);
const from = srsDao.getBySrsId(tileMatrixSetSrsId).projection;
tileMatrixSetBoundingBox = tileMatrixSetBoundingBox.projectBoundingBox(from, ProjectionConstants.EPSG_4326);
}
const tileMatrixSet = this.createTileTableWithTableName(
tableName,
contentsBoundingBox,
wgs84SrsId,
tileMatrixSetBoundingBox,
wgs84SrsId,
);

this.createStandardWGS84TileMatrix(tileMatrixSetBoundingBox, tileMatrixSet, minZoom, maxZoom, tileSize);
return tileMatrixSet;
}
/**
* Create the tables and rows necessary to store tiles in a {@link module:tiles/matrixset~TileMatrixSet}.
* This will create a [tile matrix row]{@link module:tiles/matrix~TileMatrix}
* for every integral zoom level in the range `[minZoom..maxZoom]`.
*
* @param {BoundingBox} wgs84BoundingBox
* @param {TileMatrixSet} tileMatrixSet
* @param {number} minZoom
* @param {number} maxZoom
* @param {number} [tileSize=256] optional tile size in pixels
* @returns {module:geoPackage~GeoPackage} `this` `GeoPackage`
*/
createStandardWGS84TileMatrix(
wgs84BoundingBox: BoundingBox,
tileMatrixSet: TileMatrixSet,
minZoom: number,
maxZoom: number,
tileSize = 256,
): GeoPackage {
tileSize = tileSize || 256;
const tileMatrixDao = this.tileMatrixDao;
for (let zoom = minZoom; zoom <= maxZoom; zoom++) {
this.createWGS84TileMatrixRow(wgs84BoundingBox, tileMatrixSet, tileMatrixDao, zoom, tileSize);
}
return this;
}
/**
* Create the [tables and rows](https://www.geopackage.org/spec121/index.html#tiles)
* necessary to store tiles according to the ubiquitous [XYZ web/slippy-map tiles](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames) scheme.
Expand Down Expand Up @@ -1296,6 +1399,43 @@ export class GeoPackage {
return this;
}

/**
* Adds row to tileMatrixDao
*
* @param {BoundingBox} epsg4326TileBoundingBox
* @param {TileMatrixSet} tileMatrixSet
* @param {TileMatrixDao} tileMatrixDao
* @param {number} zoomLevel
* @param {number} [tileSize=256]
* @returns {number}
* @memberof GeoPackage
*/
createWGS84TileMatrixRow(
epsg4326TileBoundingBox: BoundingBox,
tileMatrixSet: TileMatrixSet,
tileMatrixDao: TileMatrixDao,
zoomLevel: number,
tileSize = 256,
): number {
const box = TileBoundingBoxUtils.wgs84TileBox(epsg4326TileBoundingBox, zoomLevel);
const matrixWidth = box.maxLongitude - box.minLongitude + 1;
const matrixHeight = box.maxLatitude - box.minLatitude + 1;
const pixelXSize =
(epsg4326TileBoundingBox.maxLongitude - epsg4326TileBoundingBox.minLongitude) / matrixWidth / tileSize;
const pixelYSize =
(epsg4326TileBoundingBox.maxLatitude - epsg4326TileBoundingBox.minLatitude) / matrixHeight / tileSize;
const tileMatrix = new TileMatrix();
tileMatrix.table_name = tileMatrixSet.table_name;
tileMatrix.zoom_level = zoomLevel;
tileMatrix.matrix_width = matrixWidth;
tileMatrix.matrix_height = matrixHeight;
tileMatrix.tile_width = tileSize;
tileMatrix.tile_height = tileSize;
tileMatrix.pixel_x_size = pixelXSize;
tileMatrix.pixel_y_size = pixelYSize;
return tileMatrixDao.create(tileMatrix);
}

/**
* Adds row to tileMatrixDao
*
Expand Down
31 changes: 26 additions & 5 deletions lib/projection/projection.ts
@@ -1,4 +1,5 @@
import proj4 from 'proj4';
import isEqual from 'lodash/isEqual';
import { ProjectionConstants } from './projectionConstants';

export class Projection {
Expand All @@ -11,7 +12,7 @@ export class Projection {
}
}

static loadProjections(items: {name: string, definition: string | proj4.ProjectionDefinition}[]): void {
static loadProjections(items: { name: string; definition: string | proj4.ProjectionDefinition }[]): void {
if (!items) throw new Error('Invalid array of projections');
for (let i = 0; i < items.length; i++) {
if (!items[i] || !items[i].name || !items[i].definition) {
Expand All @@ -37,10 +38,10 @@ export class Projection {
*/
static getConverter(from: string, to?: string): proj4.Converter {
if (from != null && proj4(from) == null) {
throw new Error('Projection ' + from + ' has not been defined.')
throw new Error('Projection ' + from + ' has not been defined.');
}
if (to != null && proj4(to) == null) {
throw new Error('Projection ' + to + ' has not been defined.')
throw new Error('Projection ' + to + ' has not been defined.');
}
return proj4(from, to);
}
Expand All @@ -54,10 +55,10 @@ export class Projection {
*/
static convertCoordinates(from: string, to: string, coordinates: any): proj4.Converter {
if (from != null && proj4(from) == null) {
throw new Error('Projection ' + from + ' has not been defined.')
throw new Error('Projection ' + from + ' has not been defined.');
}
if (to != null && proj4(to) == null) {
throw new Error('Projection ' + to + ' has not been defined.')
throw new Error('Projection ' + to + ' has not been defined.');
}
return proj4(from, to, coordinates);
}
Expand All @@ -69,4 +70,24 @@ export class Projection {
static getWebMercatorToWGS84Converter(): proj4.Converter {
return proj4(ProjectionConstants.EPSG_3857);
}

static isWebMercator(proj: string | proj4.Converter): boolean {
if (typeof proj === 'string') {
return proj.toUpperCase() === ProjectionConstants.EPSG_3857;
} else {
return this.convertersMatch(this.getEPSGConverter(ProjectionConstants.EPSG_CODE_3857), proj);
}
}

static isWGS84(proj: string | proj4.Converter): boolean {
if (typeof proj === 'string') {
return proj.toUpperCase() === ProjectionConstants.EPSG_4326;
} else {
return this.convertersMatch(this.getEPSGConverter(ProjectionConstants.EPSG_CODE_4326), proj);
}
}

static convertersMatch(converterA: any, converterB: any): boolean {
return isEqual(converterA.oProj, converterB.oProj);
}
}
3 changes: 2 additions & 1 deletion lib/projection/projectionConstants.ts
Expand Up @@ -19,5 +19,6 @@ export class ProjectionConstants {
public static readonly WEB_MERCATOR_MAX_LON_RANGE: number = 180.0;
public static readonly WEB_MERCATOR_MIN_LON_RANGE: number = -180.0;
public static readonly WEB_MERCATOR_HALF_WORLD_WIDTH: number = 20037508.342789244;

public static readonly WGS84_HALF_WORLD_LON_WIDTH: number = 180.0;
public static readonly WGS84_HALF_WORLD_LAT_HEIGHT: number = 90.0;
}

0 comments on commit 7719874

Please sign in to comment.