@tmlmobilidade/geo 20251202.1817.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ import { type LineString } from 'geojson';
2
+ /**
3
+ * This function takes a GeoJSON LineString and splits it into equal chunks of a given precision
4
+ * using a custom implementation. This is useful to ensure greater precision when calculating the nearest
5
+ * point on a path, as some paths may have very long "straight" segments with only two points.
6
+ * @param line The LineString to be split into chunks.
7
+ * @param segmentLength The length of each segment in meters.
8
+ * @returns A GeoJSON LineString with the split chunks.
9
+ */
10
+ export declare function chunkLineByDistance(line: LineString, segmentLength: number): LineString;
@@ -0,0 +1,75 @@
1
+ /* * */
2
+ import { toLineStringFromPositions } from './conversions.js';
3
+ import { getDistanceBetweenPositions, interpolatePositions } from './measurements.js';
4
+ /**
5
+ * This function takes a GeoJSON LineString and splits it into equal chunks of a given precision
6
+ * using a custom implementation. This is useful to ensure greater precision when calculating the nearest
7
+ * point on a path, as some paths may have very long "straight" segments with only two points.
8
+ * @param line The LineString to be split into chunks.
9
+ * @param segmentLength The length of each segment in meters.
10
+ * @returns A GeoJSON LineString with the split chunks.
11
+ */
12
+ export function chunkLineByDistance(line, segmentLength) {
13
+ //
14
+ //
15
+ // Exit early if the line is empty
16
+ if (line.coordinates.length < 2)
17
+ return line;
18
+ //
19
+ // Setup variables to hold the coordinates of the chunked line
20
+ const chunkedLineCoordinates = [];
21
+ //
22
+ // Add the first point to the chunked line
23
+ chunkedLineCoordinates.push(line.coordinates[0]);
24
+ //
25
+ // Loop through the coordinates of the line
26
+ for (let i = 0; i < line.coordinates.length - 1; i++) {
27
+ // Extract the coordinates of the current and the next point
28
+ const [lngA, latA] = line.coordinates[i];
29
+ const [lngB, latB] = line.coordinates[i + 1];
30
+ // Calculate the length of the current segment
31
+ const currentSegmentLength = getDistanceBetweenPositions(line.coordinates[i], line.coordinates[i + 1]);
32
+ // If the current segment length is greater than the desired segment length
33
+ if (currentSegmentLength > segmentLength) {
34
+ // Calculate the number of segments needed to fill the current segment
35
+ const segmentsNeeded = Math.floor(currentSegmentLength / segmentLength);
36
+ // Calculate the length of each chunked segment
37
+ const chunkedSegmentLength = currentSegmentLength / segmentsNeeded;
38
+ // Calculate the remaining distance to be filled
39
+ let remainingDist = segmentLength;
40
+ // Loop through the segments needed
41
+ for (let j = 0; j < segmentsNeeded; j++) {
42
+ // Calculate the ratio of the segment
43
+ const ratio = remainingDist / chunkedSegmentLength;
44
+ // Interpolate the coordinates of the segment
45
+ const interpolated = interpolatePositions([lngA, latA], [lngB, latB], ratio);
46
+ // Add the interpolated coordinates to the chunked line
47
+ chunkedLineCoordinates.push(interpolated);
48
+ // Update the remaining distance to be filled
49
+ remainingDist -= chunkedSegmentLength;
50
+ }
51
+ // Add the last point of the segment to the chunked line
52
+ const lastPoint = line.coordinates[i + 1];
53
+ if (chunkedLineCoordinates[chunkedLineCoordinates.length - 1][0] !== lastPoint[0] || chunkedLineCoordinates[chunkedLineCoordinates.length - 1][1] !== lastPoint[1]) {
54
+ chunkedLineCoordinates.push(lastPoint);
55
+ }
56
+ }
57
+ else {
58
+ // If the current segment length is less than or equal to the desired segment length
59
+ // Add the current point to the chunked line
60
+ if (chunkedLineCoordinates[chunkedLineCoordinates.length - 1][0] !== line.coordinates[i][0] || chunkedLineCoordinates[chunkedLineCoordinates.length - 1][1] !== line.coordinates[i][1]) {
61
+ chunkedLineCoordinates.push(line.coordinates[i]);
62
+ }
63
+ }
64
+ }
65
+ //
66
+ // Add the last point of the line to the chunked line
67
+ const lastPoint = line.coordinates[line.coordinates.length - 1];
68
+ if (chunkedLineCoordinates[chunkedLineCoordinates.length - 1][0] !== lastPoint[0] || chunkedLineCoordinates[chunkedLineCoordinates.length - 1][1] !== lastPoint[1]) {
69
+ chunkedLineCoordinates.push(lastPoint);
70
+ }
71
+ //
72
+ // Return the chunked line as a GeoJSON LineString
73
+ return toLineStringFromPositions(chunkedLineCoordinates);
74
+ //
75
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Earth's radius in meters.
3
+ */
4
+ export declare const EARTH_RADIUS = 6371000;
5
+ /**
6
+ * Approximate number of meters per degree of latitude.
7
+ */
8
+ export declare const METERS_PER_DEGREE = 111320;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Earth's radius in meters.
3
+ */
4
+ export const EARTH_RADIUS = 6_371_000;
5
+ /**
6
+ * Approximate number of meters per degree of latitude.
7
+ */
8
+ export const METERS_PER_DEGREE = 111_320;
@@ -0,0 +1,36 @@
1
+ import { type HashedShape } from '@tmlmobilidade/types';
2
+ import { type Feature, type GeoJsonProperties, type LineString, type Point, type Polygon, type Position } from 'geojson';
3
+ /**
4
+ * Converts a list of GTFS shape points into a GeoJSON LineString.
5
+ * @param hashedShape The hashed shape containing GTFS shape points.
6
+ * @returns GeoJSON LineString feature
7
+ */
8
+ export declare function toLineStringFromHashedShape(hashedShape: HashedShape): LineString;
9
+ /**
10
+ * Creates a new LineString from the given set of coordinates.
11
+ * @param positions An array of coordinate positions for the LineString. Defaults to an empty array.
12
+ * @returns A GeoJSON LineString with the specified coordinates.
13
+ */
14
+ export declare function toLineStringFromPositions(positions?: Position[]): LineString;
15
+ /**
16
+ * Creates a new Point from the given coordinates.
17
+ * @param position A coordinate position for the Point. Defaults to an empty array.
18
+ * @returns A GeoJSON Point with the specified coordinates.
19
+ */
20
+ export declare function toPointFromPositions(position?: Position): Point;
21
+ /**
22
+ * Creates a new Feature from the given object and properties.
23
+ * @param object The object to convert to a Feature. Can be a LineString, Point, or Polygon.
24
+ * @param properties Optional properties to attach to the Feature. Defaults to an empty object.
25
+ * @returns A GeoJSON Feature with the specified object and properties.
26
+ */
27
+ export declare function toFeatureFromObject<T extends LineString | Point | Polygon>(object: T, properties?: GeoJsonProperties): Feature<T>;
28
+ /**
29
+ * Converts a value in kilometers or meters to meters based on a ballpark value.
30
+ * If the ballpark value is greater than 800 meters, it assumes the value is in meters.
31
+ * Otherwise, it assumes the value is in kilometers and converts it to meters.
32
+ * @param value The value to be converted, which can be a number or a string that can be converted to a number.
33
+ * @param ballpark The ballpark value to determine the unit of the value, which can also be a number or a string that can be converted to a number.
34
+ * @returns The converted value in meters.
35
+ */
36
+ export declare function toMetersFromKilometersOrMeters(value: number | string, ballpark: number | string): number;
@@ -0,0 +1,79 @@
1
+ /* * */
2
+ /**
3
+ * Converts a list of GTFS shape points into a GeoJSON LineString.
4
+ * @param hashedShape The hashed shape containing GTFS shape points.
5
+ * @returns GeoJSON LineString feature
6
+ */
7
+ export function toLineStringFromHashedShape(hashedShape) {
8
+ // Exit if no points are provided
9
+ if (!hashedShape.points?.length)
10
+ return toLineStringFromPositions([]);
11
+ // Sort points by shape_pt_sequence
12
+ const sortedPoints = [...hashedShape.points].sort((a, b) => a.shape_pt_sequence - b.shape_pt_sequence);
13
+ // Create a LineString feature
14
+ const coordinates = sortedPoints.map(p => [Number(p.shape_pt_lon), Number(p.shape_pt_lat)]);
15
+ // Return the LineString feature
16
+ return toLineStringFromPositions(coordinates);
17
+ }
18
+ /**
19
+ * Creates a new LineString from the given set of coordinates.
20
+ * @param positions An array of coordinate positions for the LineString. Defaults to an empty array.
21
+ * @returns A GeoJSON LineString with the specified coordinates.
22
+ */
23
+ export function toLineStringFromPositions(positions = []) {
24
+ return {
25
+ coordinates: positions,
26
+ type: 'LineString',
27
+ };
28
+ }
29
+ /**
30
+ * Creates a new Point from the given coordinates.
31
+ * @param position A coordinate position for the Point. Defaults to an empty array.
32
+ * @returns A GeoJSON Point with the specified coordinates.
33
+ */
34
+ export function toPointFromPositions(position = []) {
35
+ return {
36
+ coordinates: position,
37
+ type: 'Point',
38
+ };
39
+ }
40
+ /**
41
+ * Creates a new Feature from the given object and properties.
42
+ * @param object The object to convert to a Feature. Can be a LineString, Point, or Polygon.
43
+ * @param properties Optional properties to attach to the Feature. Defaults to an empty object.
44
+ * @returns A GeoJSON Feature with the specified object and properties.
45
+ */
46
+ export function toFeatureFromObject(object, properties) {
47
+ return {
48
+ geometry: object,
49
+ properties: properties || {},
50
+ type: 'Feature',
51
+ };
52
+ }
53
+ /**
54
+ * Converts a value in kilometers or meters to meters based on a ballpark value.
55
+ * If the ballpark value is greater than 800 meters, it assumes the value is in meters.
56
+ * Otherwise, it assumes the value is in kilometers and converts it to meters.
57
+ * @param value The value to be converted, which can be a number or a string that can be converted to a number.
58
+ * @param ballpark The ballpark value to determine the unit of the value, which can also be a number or a string that can be converted to a number.
59
+ * @returns The converted value in meters.
60
+ */
61
+ export function toMetersFromKilometersOrMeters(value, ballpark) {
62
+ //
63
+ const BALLPARK_THRESHOLD = 800; // meters
64
+ const valueAsNumber = Number(value);
65
+ const ballparkAsNumber = Number(ballpark);
66
+ if (Number.isNaN(valueAsNumber))
67
+ throw new Error('Value must be a number or a string that can be converted to a number.');
68
+ if (Number.isNaN(ballparkAsNumber))
69
+ throw new Error('Ballpark must be a number or a string that can be converted to a number.');
70
+ // If the ballpark is bigger than 800, then the value is in meters
71
+ // Otherwise, the value is in kilometers. This is because it is unlikely
72
+ // that a trip will be smaller than 800 meters, and longer than 800 kilometers.
73
+ if (ballparkAsNumber > BALLPARK_THRESHOLD) {
74
+ return valueAsNumber;
75
+ }
76
+ return valueAsNumber * 1000;
77
+ //
78
+ }
79
+ ;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Checks if the given latitude value is valid given Portugal limits.
3
+ * @param value The latitude value to check.
4
+ * @returns The clamped latitude value if valid, false otherwise.
5
+ */
6
+ export declare function isValidLatitude(value: number): false | number;
7
+ /**
8
+ * Checks if the given longitude value is valid given Portugal limits.
9
+ * @param value The longitude value to check.
10
+ * @returns The clamped longitude value if valid, false otherwise.
11
+ */
12
+ export declare function isValidLongitude(value: number): false | number;
13
+ /**
14
+ * Checks if the given latitude and longitude values form a valid coordinate pair.
15
+ * @param lat The latitude value to check.
16
+ * @param lng The longitude value to check.
17
+ * @returns True if the coordinate pair is valid, false otherwise.
18
+ */
19
+ export declare function isValidCoordinatePair(lat: number, lng: number): boolean;
20
+ /**
21
+ * Clamps a coordinate value to 6 decimal places.
22
+ * @param value The coordinate value to clamp.
23
+ * @returns The clamped coordinate value.
24
+ */
25
+ export declare function clampCoordinate(value: number): number;
26
+ /**
27
+ * Parses a coordinate pair string in the following formats:
28
+ * - `lat, lng`
29
+ * - `lat lng` (with a space or a tab)
30
+ * @param input The coordinate pair string to parse.
31
+ * @param clamp Whether to clamp the latitude and longitude values to 6 decimal places.
32
+ * @returns The parsed coordinates as an object, or null if the input is invalid.
33
+ */
34
+ export declare const parseCoordinatePairString: (input: string, clamp?: boolean) => null | {
35
+ lat: number;
36
+ lng: number;
37
+ };
@@ -0,0 +1,64 @@
1
+ /* * */
2
+ /**
3
+ * Checks if the given latitude value is valid given Portugal limits.
4
+ * @param value The latitude value to check.
5
+ * @returns The clamped latitude value if valid, false otherwise.
6
+ */
7
+ export function isValidLatitude(value) {
8
+ const hasValue = value !== undefined && value !== null;
9
+ const isWithinPortugal = value >= 36.9 && value <= 42.0;
10
+ if (!hasValue || !isWithinPortugal)
11
+ return false;
12
+ return clampCoordinate(value);
13
+ }
14
+ /**
15
+ * Checks if the given longitude value is valid given Portugal limits.
16
+ * @param value The longitude value to check.
17
+ * @returns The clamped longitude value if valid, false otherwise.
18
+ */
19
+ export function isValidLongitude(value) {
20
+ const hasValue = value !== undefined && value !== null;
21
+ const isWithinPortugal = value >= -9.5 && value <= -6.0;
22
+ if (!hasValue || !isWithinPortugal)
23
+ return false;
24
+ return clampCoordinate(value);
25
+ }
26
+ /**
27
+ * Checks if the given latitude and longitude values form a valid coordinate pair.
28
+ * @param lat The latitude value to check.
29
+ * @param lng The longitude value to check.
30
+ * @returns True if the coordinate pair is valid, false otherwise.
31
+ */
32
+ export function isValidCoordinatePair(lat, lng) {
33
+ const isValidLat = isValidLatitude(lat);
34
+ const isValidLng = isValidLongitude(lng);
35
+ return !!isValidLat && !!isValidLng;
36
+ }
37
+ /**
38
+ * Clamps a coordinate value to 6 decimal places.
39
+ * @param value The coordinate value to clamp.
40
+ * @returns The clamped coordinate value.
41
+ */
42
+ export function clampCoordinate(value) {
43
+ return parseFloat(value.toFixed(6));
44
+ }
45
+ /**
46
+ * Parses a coordinate pair string in the following formats:
47
+ * - `lat, lng`
48
+ * - `lat lng` (with a space or a tab)
49
+ * @param input The coordinate pair string to parse.
50
+ * @param clamp Whether to clamp the latitude and longitude values to 6 decimal places.
51
+ * @returns The parsed coordinates as an object, or null if the input is invalid.
52
+ */
53
+ export const parseCoordinatePairString = (input, clamp = true) => {
54
+ const regex = /^\s*([+-]?\d+(?:\.\d+)?)\s*(?:,|\s)\s*([+-]?\d+(?:\.\d+)?)\s*$/;
55
+ const match = input.match(regex);
56
+ if (!match)
57
+ return null;
58
+ const lat = parseFloat(match[1]);
59
+ const lng = parseFloat(match[2]);
60
+ if (clamp)
61
+ return isValidCoordinatePair(lat, lng) ? { lat: clampCoordinate(lat), lng: clampCoordinate(lng) } : null;
62
+ else
63
+ return isValidCoordinatePair(lat, lng) ? { lat, lng } : null;
64
+ };
@@ -0,0 +1,10 @@
1
+ import { type LineString } from 'geojson';
2
+ /**
3
+ * Cuts a LineString at a specified length.
4
+ * If the line is shorter than the specified length, it returns the entire line.
5
+ * @param line The LineString to cut.
6
+ * @param length The length at which to cut the line, in meters, from the start of the line.
7
+ * @param direction The direction in which to cut the line. If 'forward', it cuts from the start of the line.
8
+ * @returns A new LineString that is cut at the specified length.
9
+ */
10
+ export declare function cutLineStringAtLength(line: LineString, length: number, direction?: 'forward' | 'reversed'): LineString;
@@ -0,0 +1,68 @@
1
+ /* * */
2
+ import { toLineStringFromPositions } from './conversions.js';
3
+ import { getDistanceBetweenPositions, interpolatePositions } from './measurements.js';
4
+ /**
5
+ * Cuts a LineString at a specified length.
6
+ * If the line is shorter than the specified length, it returns the entire line.
7
+ * @param line The LineString to cut.
8
+ * @param length The length at which to cut the line, in meters, from the start of the line.
9
+ * @param direction The direction in which to cut the line. If 'forward', it cuts from the start of the line.
10
+ * @returns A new LineString that is cut at the specified length.
11
+ */
12
+ export function cutLineStringAtLength(line, length, direction = 'forward') {
13
+ //
14
+ //
15
+ // Return the line if it is empty
16
+ if (line.coordinates.length < 2) {
17
+ return line;
18
+ }
19
+ //
20
+ // Return an empty line if the length is equal to 0
21
+ if (length <= 0) {
22
+ return toLineStringFromPositions([]);
23
+ }
24
+ //
25
+ // Reverse the line if the direction is 'reversed'
26
+ if (direction === 'reversed') {
27
+ line.coordinates = line.coordinates.slice().reverse();
28
+ }
29
+ //
30
+ // Hold the cumulative distance between points
31
+ // and the coordinates of the new line
32
+ let cumulativeLength = 0;
33
+ const newLinePositions = [];
34
+ //
35
+ // Loop through the coordinates of the line
36
+ for (let i = 0; i < line.coordinates.length - 1; i++) {
37
+ // Get the coordinates of the current and the next point
38
+ const coordA = line.coordinates[i];
39
+ const coordB = line.coordinates[i + 1];
40
+ // Calculate the distance between the two points
41
+ const segmentLength = getDistanceBetweenPositions(coordA, coordB);
42
+ // If the current segment length plus the cumulative length
43
+ // extends the desired length of the resulting string
44
+ // then the line should be cut at the interpolation point
45
+ if (cumulativeLength + segmentLength >= length) {
46
+ const remainingLength = length - cumulativeLength;
47
+ const relativePositionOnSegment = remainingLength / segmentLength;
48
+ const interpolated = interpolatePositions(coordA, coordB, relativePositionOnSegment);
49
+ newLinePositions.push(coordA, interpolated);
50
+ return toLineStringFromPositions(newLinePositions);
51
+ }
52
+ // Add the length of the current segment to the
53
+ // cumulative length of the line up until this point
54
+ cumulativeLength += segmentLength;
55
+ // If the cumulative length is already greater than the desired length
56
+ // then the line should be cut at this point. Break the loop now.
57
+ if (cumulativeLength > length)
58
+ break;
59
+ // If the cumulative length is not yet up to the desired length
60
+ // then the current point should be added to the new line
61
+ newLinePositions.push(coordA);
62
+ }
63
+ //
64
+ // If the entire line is shorter than the target length
65
+ newLinePositions.push(line.coordinates[line.coordinates.length - 1]);
66
+ return toLineStringFromPositions(newLinePositions);
67
+ //
68
+ }
@@ -0,0 +1,11 @@
1
+ import { type Feature, type FeatureCollection, type Geometry } from 'geojson';
2
+ /**
3
+ * Creates a base GeoJSON feature collection for the given feature type.
4
+ * @returns A base GeoJSON feature collection with an empty features array.
5
+ */
6
+ export declare function getBaseGeoJsonFeature<T extends Geometry, K>(type: T['type'], properties?: K): Feature<T, K>;
7
+ /**
8
+ * Creates a base GeoJSON feature collection for the given feature type.
9
+ * @returns A base GeoJSON feature collection with an empty features array.
10
+ */
11
+ export declare function getBaseGeoJsonFeatureCollection<T extends Geometry, K>(): FeatureCollection<T, K>;
@@ -0,0 +1,29 @@
1
+ /* * */
2
+ /**
3
+ * Creates a base GeoJSON feature collection for the given feature type.
4
+ * @returns A base GeoJSON feature collection with an empty features array.
5
+ */
6
+ export function getBaseGeoJsonFeature(type, properties) {
7
+ const emptyGeometries = {
8
+ GeometryCollection: { geometries: [], type: 'GeometryCollection' },
9
+ LineString: { coordinates: [], type: 'LineString' },
10
+ MultiLineString: { coordinates: [], type: 'MultiLineString' },
11
+ MultiPoint: { coordinates: [], type: 'MultiPoint' },
12
+ MultiPolygon: { coordinates: [], type: 'MultiPolygon' },
13
+ Point: { coordinates: [0, 0], type: 'Point' },
14
+ Polygon: { coordinates: [], type: 'Polygon' },
15
+ };
16
+ return {
17
+ geometry: emptyGeometries[type],
18
+ properties: (properties ?? {}),
19
+ type: 'Feature',
20
+ };
21
+ }
22
+ /**
23
+ * Creates a base GeoJSON feature collection for the given feature type.
24
+ * @returns A base GeoJSON feature collection with an empty features array.
25
+ */
26
+ export function getBaseGeoJsonFeatureCollection() {
27
+ return Object.assign({ features: [], type: 'FeatureCollection' });
28
+ }
29
+ ;
@@ -0,0 +1,17 @@
1
+ import { type Feature, type Point, type Polygon } from 'geojson';
2
+ /**
3
+ * Create a geofence around a given point with a given radius in meters (default is 50 meters).
4
+ * @param point A GeoJSON Point representation of the point to create the geofence around.
5
+ * @param radius The distance in meters to calculate the geofence radius. Default is 50 meters.
6
+ * @param steps The number of steps to use for the buffer. Default is 16.
7
+ * @returns The GeoJSON Feature of a Polygon.
8
+ */
9
+ export declare function getGeofenceOnPoint(point: Feature<Point>, radius?: number, steps?: number): Feature<Polygon>;
10
+ /**
11
+ * Create a geofence around a given position with a given radius in meters (default is 50 meters).
12
+ * @param position A tuple representing the coordinates of the point to create the geofence around.
13
+ * @param radius The distance in meters to calculate the geofence radius. Default is 50 meters.
14
+ * @param steps The number of steps to use for the buffer. Default is 16.
15
+ * @returns The GeoJSON Feature of a Polygon.
16
+ */
17
+ export declare function getGeofenceOnPosition(position: [number, number], radius?: number, steps?: number): Feature<Polygon>;
@@ -0,0 +1,46 @@
1
+ /* * */
2
+ import { EARTH_RADIUS } from './constants.js';
3
+ import { toFeatureFromObject, toPointFromPositions } from './conversions.js';
4
+ import { polygon } from '@turf/turf';
5
+ /**
6
+ * Create a geofence around a given point with a given radius in meters (default is 50 meters).
7
+ * @param point A GeoJSON Point representation of the point to create the geofence around.
8
+ * @param radius The distance in meters to calculate the geofence radius. Default is 50 meters.
9
+ * @param steps The number of steps to use for the buffer. Default is 16.
10
+ * @returns The GeoJSON Feature of a Polygon.
11
+ */
12
+ export function getGeofenceOnPoint(point, radius = 50, steps = 16) {
13
+ // Extract the center coordinates from the point
14
+ const [centerLon, centerLat] = point.geometry.coordinates;
15
+ // Set the angle size based on the number of steps
16
+ const angleStep = (2 * Math.PI) / steps;
17
+ // Set an empty array to hold the coordinates of the polygon vertices
18
+ const coords = [];
19
+ // Calculate the coordinates of the polygon vertices
20
+ for (let i = 0; i < steps; i++) {
21
+ const angle = i * angleStep;
22
+ const dx = radius * Math.cos(angle);
23
+ const dy = radius * Math.sin(angle);
24
+ const newLat = centerLat + (dy / EARTH_RADIUS) * (180 / Math.PI);
25
+ const newLng = centerLon + (dx / (EARTH_RADIUS * Math.cos((centerLat * Math.PI) / 180))) * (180 / Math.PI);
26
+ coords.push([newLng, newLat]);
27
+ }
28
+ // Close the polygon by adding the first coordinate to the end
29
+ coords.push(coords[0]);
30
+ // Return the polygon feature
31
+ return polygon([coords]);
32
+ }
33
+ /**
34
+ * Create a geofence around a given position with a given radius in meters (default is 50 meters).
35
+ * @param position A tuple representing the coordinates of the point to create the geofence around.
36
+ * @param radius The distance in meters to calculate the geofence radius. Default is 50 meters.
37
+ * @param steps The number of steps to use for the buffer. Default is 16.
38
+ * @returns The GeoJSON Feature of a Polygon.
39
+ */
40
+ export function getGeofenceOnPosition(position, radius = 50, steps = 16) {
41
+ // Create a point feature from the coordinates
42
+ const newPoint = toPointFromPositions(position);
43
+ const newFeature = toFeatureFromObject(newPoint);
44
+ // Call the getGeofenceOnPoint function to create the geofence
45
+ return getGeofenceOnPoint(newFeature, radius, steps);
46
+ }
@@ -0,0 +1,9 @@
1
+ export * from './chunk-line.js';
2
+ export * from './constants.js';
3
+ export * from './conversions.js';
4
+ export * from './coordinates.js';
5
+ export * from './cut-line-at-length.js';
6
+ export * from './geojson-collections.js';
7
+ export * from './get-geofence-on-point.js';
8
+ export * from './is-point-in-polygon.js';
9
+ export * from './measurements.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export * from './chunk-line.js';
2
+ export * from './constants.js';
3
+ export * from './conversions.js';
4
+ export * from './coordinates.js';
5
+ export * from './cut-line-at-length.js';
6
+ export * from './geojson-collections.js';
7
+ export * from './get-geofence-on-point.js';
8
+ export * from './is-point-in-polygon.js';
9
+ export * from './measurements.js';
@@ -0,0 +1,9 @@
1
+ import { type Feature, type Point, type Polygon, type Position } from 'geojson';
2
+ /**
3
+ * Check if a point is inside a polygon using the ray-casting algorithm.
4
+ * @param point A GeoJSON Point representation of the point to check.
5
+ * @param geofence A GeoJSON Polygon representation of the geofence.
6
+ * @returns A boolean indicating if the point is inside the polygon.
7
+ * @see https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm
8
+ */
9
+ export declare function isPointInPolygon(point: Feature<Point> | Position, polygon: Feature<Polygon>): boolean;
@@ -0,0 +1,37 @@
1
+ /* * */
2
+ /**
3
+ * Check if a point is inside a polygon using the ray-casting algorithm.
4
+ * @param point A GeoJSON Point representation of the point to check.
5
+ * @param geofence A GeoJSON Polygon representation of the geofence.
6
+ * @returns A boolean indicating if the point is inside the polygon.
7
+ * @see https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm
8
+ */
9
+ export function isPointInPolygon(point, polygon) {
10
+ //
11
+ const pt = Array.isArray(point) ? point : point.geometry.coordinates;
12
+ const [x, y] = pt;
13
+ const ring = polygon.geometry.coordinates[0]; // Outer ring only
14
+ let inside = false;
15
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
16
+ const xi = ring[i][0], yi = ring[i][1];
17
+ const xj = ring[j][0], yj = ring[j][1];
18
+ const intersectsVertically = (yi > y) !== (yj > y);
19
+ const edgeSlope = (xj - xi) / (yj - yi + 1e-10); // slope of the edge
20
+ const xIntersect = edgeSlope * (y - yi) + xi;
21
+ const isToRight = x < xIntersect;
22
+ const intersects = intersectsVertically && isToRight;
23
+ if (intersects)
24
+ inside = !inside;
25
+ }
26
+ return inside;
27
+ }
28
+ // /**
29
+ // * Check if a point is inside a geofence polygon.
30
+ // * @param point A GeoJSON Point representation of the point to check.
31
+ // * @param geofence A GeoJSON Polygon representation of the geofence.
32
+ // * @returns A boolean indicating if the point is inside the geofence polygon.
33
+ // */
34
+ // export function isInsideGeofence(point: Position, geofence: Feature<Polygon>): boolean {
35
+ // // Check if the point is inside the polygon
36
+ // return isPointInPolygon(point, geofence);
37
+ // }
@@ -0,0 +1,42 @@
1
+ import { type Point, type Position } from 'geojson';
2
+ /**
3
+ * Calculates the distance between two points, in meters.
4
+ * This is a wrapper function around the `getDistanceBetweenPositions` function.
5
+ * @param pointA The first point.
6
+ * @param pointB The second point.
7
+ * @returns The distance between the two points, in meters.
8
+ */
9
+ export declare function getDistanceBetweenPoints(pointA: Point, pointB: Point): number;
10
+ /**
11
+ * Calculates the distance between two coordinate positions, in meters.
12
+ * This function uses the Haversine formula to calculate the distance
13
+ * between two points on the Earth's surface, given their latitude and longitude.
14
+ * The formula accounts for the curvature of the Earth.
15
+ * It is important to note that this function assumes the Earth is a perfect sphere,
16
+ * which is not entirely accurate, but it provides a good approximation for small distances.
17
+ * @param positionA The first position.
18
+ * @param positionB The second position.
19
+ * @returns The distance between the two points, in meters.
20
+ */
21
+ export declare function getDistanceBetweenPositions(positionA: Position, positionB: Position): number;
22
+ /**
23
+ * Interpolates between two points at a given ratio (0..1).
24
+ * This is a wrapper function around the `interpolatePosition` function.
25
+ * @param pointA The first point.
26
+ * @param pointB The second point.
27
+ * @param ratio The ratio at which to interpolate (0 = pointA, 1 = pointB).
28
+ * @returns The interpolated point.
29
+ */
30
+ export declare function interpolatePoints(pointA: Point, pointB: Point, ratio: number): Point;
31
+ /**
32
+ * Linearly interpolates between two positions at a given ratio (0..1).
33
+ * This function is useful for calculating intermediate points
34
+ * along a line segment defined by two positions.
35
+ * @param positionA The first position.
36
+ * @param positionB The second position.
37
+ * @param ratio The ratio at which to interpolate (0 = positionA, 1 = positionB).
38
+ * A ratio of 0.5 would give the midpoint between the two positions.
39
+ * A ratio of 0.25 would give a point closer to positionA.
40
+ * @returns The interpolated position.
41
+ */
42
+ export declare function interpolatePositions(positionA: Position, positionB: Position, ratio: number): Position;
@@ -0,0 +1,71 @@
1
+ /* * */
2
+ import { METERS_PER_DEGREE } from './constants.js';
3
+ import { toPointFromPositions } from './conversions.js';
4
+ /**
5
+ * Calculates the distance between two points, in meters.
6
+ * This is a wrapper function around the `getDistanceBetweenPositions` function.
7
+ * @param pointA The first point.
8
+ * @param pointB The second point.
9
+ * @returns The distance between the two points, in meters.
10
+ */
11
+ export function getDistanceBetweenPoints(pointA, pointB) {
12
+ return getDistanceBetweenPositions(pointA.coordinates, pointB.coordinates);
13
+ }
14
+ /**
15
+ * Calculates the distance between two coordinate positions, in meters.
16
+ * This function uses the Haversine formula to calculate the distance
17
+ * between two points on the Earth's surface, given their latitude and longitude.
18
+ * The formula accounts for the curvature of the Earth.
19
+ * It is important to note that this function assumes the Earth is a perfect sphere,
20
+ * which is not entirely accurate, but it provides a good approximation for small distances.
21
+ * @param positionA The first position.
22
+ * @param positionB The second position.
23
+ * @returns The distance between the two points, in meters.
24
+ */
25
+ export function getDistanceBetweenPositions(positionA, positionB) {
26
+ // Extract coordinates from the points
27
+ const [lngA, latA] = positionA;
28
+ const [lngB, latB] = positionB;
29
+ // Calculate the average latitude
30
+ const averageLatitude = (latA + latB) / 2;
31
+ // Calculate the differences in longitude and latitude
32
+ const dx = (lngB - lngA) * METERS_PER_DEGREE * Math.cos((averageLatitude * Math.PI) / 180);
33
+ const dy = (latB - latA) * METERS_PER_DEGREE;
34
+ // Calculate the distance using the Pythagorean theorem
35
+ return Math.sqrt(dx * dx + dy * dy);
36
+ }
37
+ /**
38
+ * Interpolates between two points at a given ratio (0..1).
39
+ * This is a wrapper function around the `interpolatePosition` function.
40
+ * @param pointA The first point.
41
+ * @param pointB The second point.
42
+ * @param ratio The ratio at which to interpolate (0 = pointA, 1 = pointB).
43
+ * @returns The interpolated point.
44
+ */
45
+ export function interpolatePoints(pointA, pointB, ratio) {
46
+ const result = interpolatePositions(pointA.coordinates, pointB.coordinates, ratio);
47
+ return toPointFromPositions(result);
48
+ }
49
+ /**
50
+ * Linearly interpolates between two positions at a given ratio (0..1).
51
+ * This function is useful for calculating intermediate points
52
+ * along a line segment defined by two positions.
53
+ * @param positionA The first position.
54
+ * @param positionB The second position.
55
+ * @param ratio The ratio at which to interpolate (0 = positionA, 1 = positionB).
56
+ * A ratio of 0.5 would give the midpoint between the two positions.
57
+ * A ratio of 0.25 would give a point closer to positionA.
58
+ * @returns The interpolated position.
59
+ */
60
+ export function interpolatePositions(positionA, positionB, ratio) {
61
+ // Extract coordinates from the points
62
+ const lng = positionA[0] + (positionB[0] - positionA[0]) * ratio;
63
+ const lat = positionA[1] + (positionB[1] - positionA[1]) * ratio;
64
+ // Preserve elevation if present
65
+ if (positionA.length > 2 && positionB.length > 2) {
66
+ const alt = positionA[2] + (positionB[2] - positionA[2]) * ratio;
67
+ return [lng, lat, alt];
68
+ }
69
+ // Return the interpolated position
70
+ return [lng, lat];
71
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@tmlmobilidade/geo",
3
+ "version": "20251202.1817.5",
4
+ "author": {
5
+ "email": "iso@tmlmobilidade.pt",
6
+ "name": "TML-ISO"
7
+ },
8
+ "license": "AGPL-3.0-or-later",
9
+ "homepage": "https://github.com/tmlmobilidade/go#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/tmlmobilidade/go/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/tmlmobilidade/go.git"
16
+ },
17
+ "keywords": [
18
+ "public transit",
19
+ "tml",
20
+ "transportes metropolitanos de lisboa",
21
+ "go"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "module",
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "scripts": {
33
+ "build": "tsc && resolve-tspaths",
34
+ "lint": "eslint ./src/ && tsc --noEmit",
35
+ "lint:fix": "eslint ./src/ --fix",
36
+ "watch": "tsc-watch --onSuccess 'resolve-tspaths'"
37
+ },
38
+ "dependencies": {
39
+ "@turf/turf": "7.3.1"
40
+ },
41
+ "devDependencies": {
42
+ "@tmlmobilidade/tsconfig": "*",
43
+ "@tmlmobilidade/types": "*",
44
+ "@types/geojson": "7946.0.16",
45
+ "@types/node": "24.10.1",
46
+ "resolve-tspaths": "0.8.23",
47
+ "tsc-watch": "7.2.0",
48
+ "typescript": "5.9.3"
49
+ }
50
+ }