From 3590d2a80f2183f595db5ccc3efef45c770be5f6 Mon Sep 17 00:00:00 2001 From: Oliv <6248122+Oliv4945@users.noreply.github.com> Date: Mon, 5 Feb 2024 07:21:09 +0100 Subject: [PATCH] Add stop search by GPS boundary box (#152) --- lib/gtfs/stops.js | 22 ++++++++++--- lib/utils.js | 32 +++++++++++++++++++ test/mocha/get-stops.js | 69 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 4 deletions(-) diff --git a/lib/gtfs/stops.js b/lib/gtfs/stops.js index a95fba09..221556e8 100644 --- a/lib/gtfs/stops.js +++ b/lib/gtfs/stops.js @@ -7,6 +7,7 @@ import { formatOrderByClause, formatSelectClause, formatWhereClause, + formatWhereClauseBoundaryBox, formatWhereClauses, } from '../utils.js'; import { stopsToGeoJSON } from '../geojson-utils.js'; @@ -37,9 +38,10 @@ export function getStops(query = {}, fields = [], orderBy = [], options = {}) { const tableName = sqlString.escapeId(stops.filenameBase); const selectClause = formatSelectClause(fields); let whereClause = ''; + let whereClauses = []; const orderByClause = formatOrderByClause(orderBy); - const stopQuery = omit(query, [ + let stopQuery = omit(query, [ 'route_id', 'trip_id', 'service_id', @@ -54,9 +56,21 @@ export function getStops(query = {}, fields = [], orderBy = [], options = {}) { 'shape_id', ]); - const whereClauses = Object.entries(stopQuery).map(([key, value]) => - formatWhereClause(key, value) - ); + if (options.boundary_side_m === undefined) { + whereClauses = Object.entries(stopQuery).map(([key, value]) => + formatWhereClause(key, value) + ); + } else { + // Search inside a GPS coordinates boundary box + stopQuery = omit(query, [ + 'stop_lat', + 'stop_lon', + ]); + whereClauses.push(...Object.entries(stopQuery).map(([key, value]) => + formatWhereClause(key, value) + )); + whereClauses.push(formatWhereClauseBoundaryBox(query.stop_lat, query.stop_lon, options.boundary_side_m)) + } if (Object.values(tripQuery).length > 0) { whereClauses.push(`stop_id IN (${buildStoptimeSubquery(tripQuery)})`); diff --git a/lib/utils.js b/lib/utils.js index d347cc55..a20aed58 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -92,6 +92,38 @@ export function formatJoinClause(joinObject) { .join(' '); } +function degree2radian(angle) { + return angle * Math.PI / 180; +} + +function radian2degree(angle) { + return angle / Math.PI * 180; +} + +/* + * Search inside GPS coordinates boundary box + * Distance unit: meters + * */ +export function formatWhereClauseBoundaryBox(latitudeDegree, longitudeDegree, boxSide) { + + const earthRadius = 6371000; + latitudeDegree = parseFloat(latitudeDegree); + longitudeDegree = parseFloat(longitudeDegree); + + const latitudeRadian = degree2radian(latitudeDegree); + const radiusFromLatitude = Math.cos(latitudeRadian) * earthRadius; + + // boxSide is divided by 2 as user specify the square side size + // but we are centering the coordinates in the middle of this square + const deltaLatitude = radian2degree(boxSide / 2 / earthRadius); + const deltaLongitude = radian2degree(boxSide / 2 / radiusFromLatitude); + + let query = `stop_lat BETWEEN ${latitudeDegree - deltaLatitude} AND ${latitudeDegree + deltaLatitude}`; + query += ` AND stop_lon BETWEEN ${longitudeDegree - deltaLongitude} AND ${longitudeDegree + deltaLongitude}`; + + return query; +} + export function formatWhereClause(key, value) { if (Array.isArray(value)) { let whereClause = `${sqlString.escapeId(key)} IN (${value diff --git a/test/mocha/get-stops.js b/test/mocha/get-stops.js index d6f8a086..9be4165d 100644 --- a/test/mocha/get-stops.js +++ b/test/mocha/get-stops.js @@ -266,4 +266,73 @@ describe('getStops():', () => { ); } }); + + it('should return array of stops for specific GPS boundaries', () => { + const distance = 100; + const stopLatitude = 37.709538; + const stopLongitude = -122.401586; + + const results = getStops({ + stop_lat: stopLatitude, + stop_lon: stopLongitude, + }, + [], [], { "boundary_side_m": distance } + ); + + const expectedResult = [ + { + stop_id: 'ctba', + stop_code: null, + stop_name: 'Bayshore Caltrain', + tts_stop_name: null, + stop_desc: null, + stop_lat: 37.709544, + stop_lon: -122.401318, + zone_id: null, + stop_url: 'http://www.caltrain.com/stations/bayshorestation.html', + location_type: 1, + parent_station: null, + stop_timezone: null, + wheelchair_boarding: 1, + level_id: null, + platform_code: null + }, { + stop_id: '70032', + stop_code: '70032', + stop_name: 'Bayshore Caltrain', + tts_stop_name: null, + stop_desc: null, + stop_lat: 37.709544, + stop_lon: -122.40198, + zone_id: '1', + stop_url: 'http://www.caltrain.com/stations/bayshorestation.html', + location_type: 0, + parent_station: 'ctba', + stop_timezone: null, + wheelchair_boarding: 1, + level_id: null, + platform_code: 'SB' + }, { + stop_id: '70031', + stop_code: '70031', + stop_name: 'Bayshore Caltrain', + tts_stop_name: null, + stop_desc: null, + stop_lat: 37.709537, + stop_lon: -122.401586, + zone_id: '1', + stop_url: 'http://www.caltrain.com/stations/bayshorestation.html', + location_type: 0, + parent_station: 'ctba', + stop_timezone: null, + wheelchair_boarding: 1, + level_id: null, + platform_code: 'NB' + } + ]; + + should.exist(results); + results.length.should.equal(3); + results.should.match(expectedResult); + }); });