Skip to content

Commit

Permalink
gbfs v3 complete; gbfs 2 and 1 setup
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr Martian committed Dec 27, 2024
1 parent efd718f commit 0dd0989
Show file tree
Hide file tree
Showing 87 changed files with 21,178 additions and 7 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
"@types/node": "^22.10.2",
"@types/tmp": "^0.2.6",
"@webgpu/types": "^0.1.51",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"all-the-cities": "^3.1.0",
"coveralls": "^3.1.1",
"eslint": "^9.16.0",
Expand Down
2 changes: 1 addition & 1 deletion src/geometry/ll/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { lonLatToXYZ } from '../s2/coords';
import { degToRad, radToDeg } from '../util';

import type { S1Angle } from '../s1/angle';
import type { Point, Point3D } from 's2json-spec';
import type { Point, Point3D } from '../';

/** Just another way of defining a standard 2D point. */
export type LonLat = Point;
Expand Down
2 changes: 1 addition & 1 deletion src/geometry/s1/angle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getDistance } from '../ll';
import { degToRad, radToDeg } from '..';

import type { LonLat } from '../ll';
import type { Point3D } from 's2json-spec';
import type { Point3D } from '../';

/**
* This class represents a one-dimensional angle (as opposed to a
Expand Down
2 changes: 1 addition & 1 deletion src/proj4/projections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { ProjectionBase } from './base';

import type { DatumParams } from '../../readers/wkt';
import type { GridDefinition } from '../../readers/nadgrid';
import type { VectorPoint } from 's2json-spec';
import type { VectorPoint } from '../../geometry';

export * from './aea';
export * from './aeqd';
Expand Down
6 changes: 3 additions & 3 deletions src/readers/csv/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ export class CSVReader<
* @param source - the source of the CSV data
* @returns - an object with key-value pairs whose keys and values are both strings
*/
export function parseCSVAsRecord(source: string): Record<string, string>[] {
const res: Record<string, string>[] = [];
export function parseCSVAsRecord<T = Record<string, string>>(source: string): T[] {
const res: T[] = [];
const split = source.split('\n');
const firstLine = split[0].split(',').map((s) => s.trim());
for (let line of split.slice(1)) {
Expand All @@ -143,7 +143,7 @@ export function parseCSVAsRecord(source: string): Record<string, string>[] {
if (value === '' || value === ' ') continue;
record[firstLine[i]] = value;
}
res.push(record);
res.push(record as T);
}

return res;
Expand Down
102 changes: 102 additions & 0 deletions src/readers/gbfs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { parseCSVAsRecord } from '..';
import { GBFSReaderV3, GBFSV3, buildGBFSReaderV3 } from './schemaV3';

export * from './schemaV3';

/** The versions of GBFS reader classes this data could be */
export type GBFSReaders = GBFSReaderV3;

/** The versions of GBFS schemas this data could be */
export type GBFSTypess = GBFSV3;

/**
* Given a link to a GBFS feed, build the appropriate reader for the feed.
* Examples:
* - v3: https://backend.citiz.fr/public/provider/9/gbfs/v3.0/gbfs.json
* - v2: https://gbfs.helbiz.com/v2.2/durham/gbfs.json
* - v1: https://gbfs.urbansharing.com/gbfs/gbfs.json
* @param url - The link to the GBFS feed
* @returns - a GBFSReader of the appropriate version
*/
export async function buildGBFSReader(url: string): Promise<GBFSReaders> {
const data = await fetch(url).then(async (res) => (await res.json()) as GBFSTypess);
const versionMajor = data.version[0];
if (versionMajor === '3') {
return await buildGBFSReaderV3(data);
} else throw Error('Unsupported GBFS version');
}

// TODO:
// - [ ] create schema for v1.1 and v2.3
// - [ ] convert each schema to typescript interfaces/enums/types
// - [ ] given a link, fetch all associated data and put in GBFSReaderV1/GBFSReaderV2/GBFSReaderV3
// - [ ] build VectorFeatures from data

/** System Definition that is returned from the github CSV file. */
export interface System {
/** [**Required**] ISO 3166-1 alpha-2 code designating the country where the system is located. */
countryCode: string;
/** [**Required**] Name of the mobility system. This MUST match the name field in system_information.json */
name: string;
/**
* [**Required**] Primary city in which the system is located, followed by the 2-letter state code
* for US systems. The location name SHOULD be in English if the location has an English name
* (eg: Brussels).
*/
location: string;
/** [**Required**] ID for the system. This MUST match the system_id field in system_information.json. */
systemId: string;
/**
* [**Required**] URL for the system from the url field in system_information.json.
* If the url field is not included in system_information.json this SHOULD be the primary URL
* for the system operator.
*/
url: string;
/** [**Required**] URL for the system's gbfs.json auto-discovery file. */
autoDiscoveryUrl: string;
/**
* [**Required**] List of GBFS version(s) under which the feed is published. Multiple values are
* separated by a semi-colon surrounded with 1 space on each side for readability (" ; ").
*/
supportedVersions: string[];
/**
* [**Conditionally Required**] If authentication is required, this MUST contain a URL to a
* human-readable page describing how the authentication should be performed and how credentials
* can be created, or directly contain the public key-value pair to append to the feed URLs.
*/
authInfo?: string;
}

/**
* Fetches the systems from the github CSV file
* @param url - The URL of the CSV file. The default is the one used by GBFS. This variable exists for testing
* @returns - an array of systems
*/
export async function fetchGTFSSystems(
url = 'https://raw.githubusercontent.com/MobilityData/gbfs/refs/heads/master/systems.csv',
): Promise<System[]> {
const res: System[] = [];
const data = await fetch(url).then(async (res) => await res.text());
const parsed = parseCSVAsRecord(data);

for (const system of parsed) {
const { Name: name, Location: location, URL: url } = system;
const countryCode = system['Country Code'];
const systemId = system['System ID'];
const autoDiscoveryUrl = system['Auto-Discovery URL'];
const supportedVersions = (system['Supported Versions'] ?? '').split(' ; ');
const authInfo = system['Authentication Info'];
res.push({
name,
location,
url,
countryCode,
systemId,
autoDiscoveryUrl,
supportedVersions,
authInfo,
});
}

return res;
}
102 changes: 102 additions & 0 deletions src/readers/gbfs/schemaV1/freeBikeStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* # GBFS Free Bike Status Schema V1
* Describes the vehicles that are not at a station and are available for rent.
*
* ## Links
* - [GBFS Specification](https://github.com/MobilityData/gbfs/blob/v1.1/gbfs.md#free_bike_statusjson)
*/
export const gbfsFreeBikeStatusSchemaV1 = {
$schema: 'http://json-schema.org/draft-07/schema',
$id: 'https://github.com/MobilityData/gbfs/blob/v1.1/gbfs.md#free_bike_statusjson',
description: 'Describes the vehicles that are not at a station and are available for rent.',
type: 'object',
properties: {
last_updated: {
description: 'Last time the data in the feed was updated in POSIX time.',
type: 'integer',
minimum: 1450155600,
},
ttl: {
description:
'Number of seconds before the data in the feed will be updated again (0 if the data should always be refreshed).',
type: 'integer',
minimum: 0,
},
version: {
description:
'GBFS version number to which the feed conforms, according to the versioning framework (added in v1.1).',
type: 'string',
const: '1.1',
},
data: {
description: 'Array that contains one object per bike as defined below.',
type: 'object',
properties: {
bikes: {
type: 'array',
items: {
type: 'object',
properties: {
bike_id: {
description: 'Identifier of a vehicle.',
type: 'string',
},
lat: {
description: 'The latitude of the vehicle.',
type: 'number',
minimum: -90,
maximum: 90,
},
lon: {
description: 'The longitude of the vehicle.',
type: 'number',
minimum: -180,
maximum: 180,
},
is_reserved: {
description: 'Is the vehicle currently reserved?',
type: 'number',
minimum: 0,
maximum: 1,
},
is_disabled: {
description: 'Is the vehicle currently disabled (broken)?',
type: 'number',
minimum: 0,
maximum: 1,
},
rental_uris: {
description:
'Contains rental uris for Android, iOS, and web in the android, ios, and web fields (added in v1.1).',
type: 'object',
properties: {
android: {
description:
'URI that can be passed to an Android app with an intent (added in v1.1).',
type: 'string',
format: 'uri',
},
ios: {
description:
'URI that can be used on iOS to launch the rental app for this vehicle (added in v1.1).',
type: 'string',
format: 'uri',
},
web: {
description:
'URL that can be used by a web browser to show more information about renting this vehicle (added in v1.1).',
type: 'string',
format: 'uri',
},
},
},
},
required: ['bike_id', 'lat', 'lon', 'is_reserved', 'is_disabled'],
},
},
},
required: ['bikes'],
},
},
required: ['last_updated', 'ttl', 'version', 'data'],
};
81 changes: 81 additions & 0 deletions src/readers/gbfs/schemaV1/gbfs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* # GBFS V1
* Auto-discovery file that links to all of the other files published by the system.
*
* ## Links
* - [GBFS Specification](https://github.com/MobilityData/gbfs/blob/v1.1/gbfs.md#gbfsjson)
*/
export const gbfsSchemaV1 = {
$schema: 'http://json-schema.org/draft-07/schema',
$id: 'https://github.com/MobilityData/gbfs/blob/v1.1/gbfs.md#gbfsjson',
description: 'Auto-discovery file that links to all of the other files publiished by the system.',
type: 'object',
properties: {
last_updated: {
description: 'Last time the data in the feed was updated in POSIX time.',
type: 'integer',
minimum: 1450155600,
},
ttl: {
description:
'Number of seconds before the data in the feed will be updated again (0 if the data should always be refreshed).',
type: 'integer',
minimum: 0,
},
version: {
description:
'GBFS version number to which the feed conforms, according to the versioning framework (added in v1.1).',
type: 'string',
const: '1.1',
},
data: {
description: 'Response data in the form of name:value pairs.',
type: 'object',
patternProperties: {
'^[a-z]{2,3}(-[A-Z]{2})?$': {
type: 'object',
properties: {
feeds: {
description:
'An array of all of the feeds that are published by the auto-discovery file. Each element in the array is an object with the keys below.',
type: 'array',
items: {
type: 'object',
properties: {
name: {
description:
'Key identifying the type of feed this is. The key must be the base file name defined in the spec for the corresponding feed type.',
type: 'string',
enum: [
'gbfs',
'gbfs_versions',
'system_information',
'station_information',
'station_status',
'free_bike_status',
'system_hours',
'system_alerts',
'system_calendar',
'system_regions',
'system_pricing_plans',
],
},
url: {
description: 'URL for the feed.',
type: 'string',
format: 'uri',
},
},
required: ['name', 'url'],
},
},
},
required: ['feeds'],
},
},
minProperties: 1,
additionalProperties: false,
},
},
required: ['last_updated', 'ttl', 'version', 'data'],
};
Loading

0 comments on commit 0dd0989

Please sign in to comment.