Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions server/src/Support/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,20 @@ public static function getPointFromMixed($coordinates): ?Point
try {
$coordinates = Point::fromJson($coordinatesJson);
} catch (\Throwable $e) {
// Both fallbacks carry a GeoJSON coordinate value, so they are
// read as GeoJSON first: pointFromGeoJson() maps [lng, lat],
// whereas recursing hands the pair to the positional array
// reader below, which takes index 0 as the latitude. Nested
// values (a Polygon or LineString ring rather than a single
// pair) yield null there and still fall through to it.
if ($coordinatesInGeoJson) {
return static::getPointFromMixed($coordinatesInGeoJson);
return static::pointFromGeoJsonCoordinates($coordinatesInGeoJson)
?? static::getPointFromMixed($coordinatesInGeoJson);
}

if ($coordinatesInGeoJsonFeature) {
return static::getPointFromMixed($coordinatesInGeoJsonFeature);
return static::pointFromGeoJsonCoordinates($coordinatesInGeoJsonFeature)
?? static::getPointFromMixed($coordinatesInGeoJsonFeature);
}
}
}
Expand Down Expand Up @@ -701,6 +709,19 @@ protected static function pointFromGeoJson($coordinates): ?Point
return new Point((float) $latitude, (float) $longitude);
}

/**
* Resolve a bare GeoJSON `coordinates` value — an `[longitude, latitude]`
* pair — into a Point, without the surrounding envelope.
*
* Returns null for anything that is not a usable pair, including the nested
* rings a Polygon or LineString carries, so callers can fall through to
* their own handling.
*/
protected static function pointFromGeoJsonCoordinates($coordinates): ?Point
{
return static::pointFromGeoJson(['type' => 'Point', 'coordinates' => $coordinates]);
}

/**
* Converts a point to a WKT (Well-Known Text) representation for SQL insert.
*
Expand Down
45 changes: 38 additions & 7 deletions server/tests/PointResolutionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,44 @@ function fleetopsBboxPoint(): array
],
]);

// NOTE: this fallback recurses with the bare coordinate pair, which the
// array reader interprets positionally as [lat, lng] — the reverse of
// GeoJSON's [lng, lat]. The pair therefore comes back transposed. This
// asserts the behaviour as it stands rather than the intent; changing it
// affects every location write path and belongs in its own change.
expect($point->getLat())->toBe(103.851)
->and($point->getLng())->toBe(1.2816);
// The nested pair is GeoJSON, so it is read as [lng, lat]
expect($point->getLat())->toBe(1.2816)
->and($point->getLng())->toBe(103.851);
});

test('point resolution falls back to top level coordinates before nested geometry', function () {
// The top-level `coordinates` arm is preferred, and is read in the same
// GeoJSON order. Point::fromJson rejects the envelope because of the extra
// member, which is what pushes resolution into the fallback at all.
$point = Utils::getPointFromMixed([
'type' => 'Point',
'coordinates' => [103.851, 1.2816],
'geometry' => [
'type' => 'Point',
'coordinates' => [1.0, 2.0],
],
]);

expect($point->getLat())->toBe(1.2816)
->and($point->getLng())->toBe(103.851);
});

test('point resolution still hands nested rings to the positional reader', function () {
// A Polygon ring is not a coordinate pair, so the GeoJSON read declines and
// resolution falls through to the existing recursion unchanged.
$point = Utils::getPointFromMixed([
'type' => 'Polygon',
'coordinates' => [
[
[103.0, 1.0],
[104.0, 1.0],
[104.0, 2.0],
[103.0, 1.0],
],
],
]);

expect($point)->toBeInstanceOf(Fleetbase\LaravelMysqlSpatial\Types\Point::class);
});

test('coordinate helper does not collapse bbox GeoJSON point to zero', function () {
Expand Down