Skip to content

Commit

Permalink
Added support for MultiPolygon Builgin shapes
Browse files Browse the repository at this point in the history
  • Loading branch information
AdenForshaw committed Nov 12, 2024
1 parent 8148d70 commit 27491c1
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 15 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

- Built using [NestJS](https://github.com/nestjs/nest) TypeScript Framework by [ThatAPICompany](https://thatapicompany.com) - specialists in all things APIs

## Demo

[See the API in Action at https://www.OvertureMapsAPI.com/](https://www.overturemapsapi.com/)


## Endpoints

- [OpenAPI Spec Doc](https://overture-maps-api.thatapicompany.com/api-docs.json)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "overture-maps-api",
"version": "0.1.5",
"version": "0.1.6",
"description": "",
"author": "",
"private": true,
Expand Down
4 changes: 2 additions & 2 deletions src/bigquery/row-parsers/bq-building-row.parser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Building } from "../../buildings/interfaces/building.interface"
import { parsePolygonToGeoJSON } from "../../utils/geojson"
import { parsePolygonToGeoJSON, parseWKTToGeoJSON } from "../../utils/geojson"

export const parseBuildingRow = (row: any): Building => {

return {

id: row.id,
geometry: parsePolygonToGeoJSON(row.geometry.value),
geometry: parseWKTToGeoJSON(row.geometry.value),
bbox: {
xmin: parseFloat(row.bbox.xmin),
xmax: parseFloat(row.bbox.xmax),
Expand Down
4 changes: 2 additions & 2 deletions src/bigquery/row-parsers/bq-place-row.parser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Place, PlaceWithBuilding } from "../../places/interfaces/place.interface"
import { parsePointToGeoJSON, parsePolygonToGeoJSON } from "../../utils/geojson"
import { parsePointToGeoJSON, parsePolygonToGeoJSON, parseWKTToGeoJSON } from "../../utils/geojson"

export const parsePlaceRow = (row: any): Place => {

Expand Down Expand Up @@ -59,7 +59,7 @@ export const parsePlaceWithBuildingRow = (row: any): PlaceWithBuilding => {
const building = {
id: row.building_id,
distance: parseFloat(row.distance_to_nearest_building),
geometry: parsePolygonToGeoJSON(row.building_geometry.value),
geometry: parseWKTToGeoJSON(row.building_geometry.value),

}
return {
Expand Down
4 changes: 2 additions & 2 deletions src/buildings/interfaces/building.interface.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Source } from "../../places/interfaces/place.interface";
import { Bbox } from "../../common/interfaces/geometry.interface";
import { Geometry, Polygon } from "geojson";
import { Geometry, MultiPolygon, Polygon ,Point} from "geojson";


export interface Building {

id: string;
geometry: Polygon;
geometry: Point|Polygon|MultiPolygon;
bbox?: Bbox;
version: string;
sources: Source[];
Expand Down
6 changes: 3 additions & 3 deletions src/places/dto/responses/place-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Place, PlaceWithBuilding } from '../../interfaces/place.interface';
import { BrandDto } from '../models/brand.dto';
import { CategoryDto } from '../models/category.dto';
import { GeometryDto } from '../../../common/dto/responses/geometry.dto';
import { Geometry, Point, Polygon } from 'geojson';
import { Geometry, MultiPolygon, Point, Polygon } from 'geojson';
import { GetByLocationDto } from '../../../common/dto/requests/get-by-location.dto';
import { applyIncludesToDto } from '../../../common/dto/responses/includes.dto';

Expand Down Expand Up @@ -114,7 +114,7 @@ export class PlacePropertiesDto {

ext_building?: {
id:string;
geometry:Polygon;
geometry:Point|Polygon|MultiPolygon;
distance:number
}

Expand All @@ -137,7 +137,7 @@ export class PlaceResponseDto {
description: 'Geometric representation of the place.',
type: () => GeometryDto,
})
geometry: Point|Polygon;
geometry: Point|Polygon|MultiPolygon;

@ApiProperty({
description: 'Properties and additional details of the place.',
Expand Down
6 changes: 3 additions & 3 deletions src/places/interfaces/place.interface.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Point, Polygon } from "geojson";
import { MultiPolygon, Point, Polygon } from "geojson";
import { Bbox } from "../../common/interfaces/geometry.interface";

export interface Place {
id: string;
geometry: Point|Polygon;
geometry: Point|Polygon|MultiPolygon;
bbox?: Bbox;
version: string;
sources: Source[];
Expand All @@ -25,7 +25,7 @@ export interface PlaceWithBuilding extends Place {

building: {
id:string
geometry: Polygon;
geometry: Point|Polygon|MultiPolygon;
distance: number;
}
}
Expand Down
44 changes: 44 additions & 0 deletions src/utils/geojson.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { parseMultiPolygonToGeoJSON, parsePolygonToGeoJSON, parseWKTToGeoJSON } from "./geojson";


const PolygonStr = "POLYGON((151.2762519003 -33.8915747100951, 151.276258302075 -33.8914594937271, 151.276189186945 -33.8914340614275, 151.276066105576 -33.8914671922852, 151.276054954362 -33.8914667647, 151.275981484004 -33.8914858830191, 151.275999470338 -33.8915335108494, 151.275962780589 -33.8915430560486, 151.275960784231 -33.8915789311803, 151.275923313259 -33.8915774943851, 151.275920283167 -33.8916319459681, 151.275975754972 -33.891634069767, 151.275983409999 -33.8916543402525, 151.275981862028 -33.8916821576985, 151.27603392474 -33.8916841540002, 151.276035202591 -33.8916611558307, 151.276077326459 -33.8916501944598, 151.276088482028 -33.891650622211, 151.276167145946 -33.8916301570429, 151.276169244963 -33.8915924371208, 151.276165426201 -33.8915823250443, 151.276215281919 -33.8915842367227, 151.2762519003 -33.8915747100951))"

const multiPolygonStr = `MULTIPOLYGON(((-73.9944007 40.7135703, -73.9943494 40.7134777, -73.9942995 40.7133877, -73.9938986 40.7135098, -73.993976 40.7136465, -73.9943661 40.7136157, -73.9943895 40.713585, -73.9944007 40.7135703)), ((-73.9942489 40.7132946, -73.9941596 40.7131396, -73.9941396 40.713141, -73.9941334 40.7131415, -73.9937179 40.713175, -73.9938473 40.7134172, -73.9942297 40.7133005, -73.9942489 40.7132946)))`

describe('GeoJSON tests', () => {


it('should parse a valid MULTIPOLYGON string to GeoJSON', () => {

try {
const geoJSON = parseMultiPolygonToGeoJSON(multiPolygonStr);
expect(geoJSON).toBeDefined();
} catch (error) {
console.error("Error parsing MULTIPOLYGON:", error.message);
//fail
expect(true).toBe(false);
}

});


it('should parse a Polygon to GeoJSON', () => {

try {
const geoJSON = parsePolygonToGeoJSON(PolygonStr);
expect(geoJSON).toBeDefined();
} catch (error) {
console.error("Error parsing MULTIPOLYGON:", error.message);
//fail
expect(true).toBe(false);
}
})

it('should check that parseWKTToGeoJSON results in the correct GeoJSON type', () => {
expect(parseWKTToGeoJSON(PolygonStr).type).toBe('Polygon');
});

it('should check that parseWKTToGeoJSON results in the correct GeoJSON type', () => {
expect(parseWKTToGeoJSON(multiPolygonStr).type).toBe('MultiPolygon');
});
})
63 changes: 61 additions & 2 deletions src/utils/geojson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,25 @@ Expect string to be in format "POLYGON((151.2762519003 -33.8915747100951, 151.27
*/
import * as turf from '@turf/turf'
import { Feature, Point, Polygon } from 'geojson';
import { Feature, MultiPolygon, Point, Polygon } from 'geojson';
//import geojson

export const parseWKTToGeoJSON = (wkt: string): Point|Polygon|MultiPolygon => {
//if MULTI use parseMultiPolygonToGeoJSON
if(wkt.includes("MULTIPOLYGON")){
console.log('function to handle parsing of MULTI e.g. ', wkt);
return parseMultiPolygonToGeoJSON(wkt);
}
//if POLYGON the use parsePolygonToGeoJSON
if(wkt.includes("POLYGON")){
return parsePolygonToGeoJSON(wkt);
}
//if POINT
if(wkt.includes("POINT")){
return parsePointToGeoJSON(wkt);
}
}

export const parsePolygonToGeoJSON = (polygon: string): Polygon => {
try{
if(polygon === undefined){
Expand All @@ -25,7 +41,7 @@ export const parsePolygonToGeoJSON = (polygon: string): Polygon => {
num = parseFloat(c);
}
if(isNaN(num)){
console.log('Coordinate is NaN', c);
console.log('Coordinate is NaN', c, polygon);
}
return num
}
Expand All @@ -48,6 +64,49 @@ export const parsePolygonToGeoJSON = (polygon: string): Polygon => {
}
}

/*
function to handle parsing of MULTI e.g. MULTI(-73.9944007 MULTIPOLYGON(((-73.9944007 40.7135703, -73.9943494 40.7134777, -73.9942995 40.7133877, -73.9938986 40.7135098, -73.993976 40.7136465, -73.9943661 40.7136157, -73.9943895 40.713585, -73.9944007 40.7135703)), ((-73.9942489 40.7132946, -73.9941596 40.7131396, -73.9941396 40.713141, -73.9941334 40.7131415, -73.9937179 40.713175, -73.9938473 40.7134172, -73.9942297 40.7133005, -73.9942489 40.7132946)))
Coordinate is NaN (-73.9942489 MULTIPOLYGON(((-73.9944007 40.7135703, -73.9943494 40.7134777, -73.9942995 40.7133877, -73.9938986 40.7135098, -73.993976 40.7136465, -73.9943661 40.7136157, -73.9943895 40.713585, -73.9944007 40.7135703)), ((-73.9942489 40.7132946, -73.9941596 40.7131396, -73.9941396 40.713141, -73.9941334 40.7131415, -73.9937179 40.713175, -73.9938473 40.7134172, -73.9942297 40.7133005, -73.9942489 40.7132946)))
*/
export const parseMultiPolygonToGeoJSON = (multiPolygonStr):MultiPolygon => {
// Regular expression to match polygons inside MULTIPOLYGON((...))
const multiPolygonRegex = /MULTIPOLYGON\s*\(\(\((.*?)\)\)\)/g;
const coordinateRegex = /(-?\d+\.\d+)\s+(-?\d+\.\d+)/g;

// Function to parse a single polygon string into GeoJSON coordinates
const parsePolygon = (polygonStr) => {
const coordinates = [];
let match;
while ((match = coordinateRegex.exec(polygonStr)) !== null) {
const [_, lon, lat] = match;
coordinates.push([parseFloat(lon), parseFloat(lat)]);
}
return coordinates;
};

// Check for MULTIPOLYGON match and iterate through each polygon set
const polygons = [];
let match;
while ((match = multiPolygonRegex.exec(multiPolygonStr)) !== null) {
const polygonStr = match[1];
const polygonCoordinates = polygonStr
.split(/\)\s*,\s*\(/) // Split into individual polygons
.map(parsePolygon); // Parse each polygon string
polygons.push(polygonCoordinates);
}

if (polygons.length === 0) {
throw new Error("No valid MULTIPOLYGON data found in input.");
}

// Construct GeoJSON object
const geoJSON = {
type: "MultiPolygon",
coordinates: polygons
};

return geoJSON as MultiPolygon;
}
/*
export string of POINT(151.2772322 -33.8913828)
*/
Expand Down

0 comments on commit 27491c1

Please sign in to comment.