Skip to content

Commit

Permalink
Merge pull request #489 from jjrom/develop
Browse files Browse the repository at this point in the history
Bunch of update (see description)
  • Loading branch information
jjrom authored Jan 17, 2025
2 parents c04429d + 18dcfdc commit 2de785e
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 55 deletions.
33 changes: 33 additions & 0 deletions admin_scripts/migrate_to_9.5.9.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/command/with-contenv php

<?php

require_once("/app/resto/core/RestoConstants.php");
require_once("/app/resto/core/RestoDatabaseDriver.php");
require_once("/app/resto/core/utils/RestoLogUtil.php");
require_once("/app/resto/core/utils/Antimeridian.php");
require_once("/app/resto/core/dbfunctions/UsersFunctions.php");

/*
* Read configuration from file...
*/
$configFile = '/etc/resto/config.php';
if ( !file_exists($configFile)) {
exit(1);
}
$config = include($configFile);
$dbDriver = new RestoDatabaseDriver($config['database'] ?? null);
$queries = [];

$antimeridian = new AntiMeridian();

$targetSchema = $dbDriver->targetSchema;

try {
$dbDriver->query('ALTER TABLE ' . $targetSchema . '.catalog_feature ADD COLUMN IF NOT EXISTS created TIMESTAMP DEFAULT now()');
} catch(Exception $e){
RestoLogUtil::httpError(500, $e->getMessage());
}
echo "Looks good\n";


2 changes: 1 addition & 1 deletion app/resto/core/RestoConstants.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class RestoConstants
// [IMPORTANT] Starting resto 7.x, default routes are defined in RestoRouter class

// resto version
const VERSION = '9.5.8';
const VERSION = '9.5.9';

/* ============================================================
* NEVER EVER TOUCH THESE VALUES
Expand Down
6 changes: 2 additions & 4 deletions app/resto/core/api/STACAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -1640,14 +1640,12 @@ private function getParentAndChilds($catalogId, $params)
$element = array(
'rel' => 'items',
'type' => RestoUtil::$contentTypes['geojson'],
'href' => $this->context->core['baseUrl'] . ( str_starts_with($catalogId, 'collections/') ? '/' : '/catalogs/') . join('/', array_map('rawurlencode', explode('/', $parentAndChilds['parent']['id']))) . '/_'
'href' => $this->context->core['baseUrl'] . ( str_starts_with($catalogId, 'collections/') ? '/' : '/catalogs/') . join('/', array_map('rawurlencode', explode('/', $parentAndChilds['parent']['id']))) . '/_',
'title' => 'All items'
);
if ( $parentAndChilds['parent']['counters']['total'] > 0 ) {
$element['matched'] = $parentAndChilds['parent']['counters']['total'];
}
if ( isset($parentAndChilds['parent']['title']) ) {
$element['title'] = $parentAndChilds['parent']['title'];
}
$parentAndChilds['childs'][] = $element;
}

Expand Down
6 changes: 3 additions & 3 deletions app/resto/core/dbfunctions/CatalogsFunctions.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ public function getCatalogItems($catalogId, $baseUrl)
* Delete (within transaction)
*/
try {
$results = $this->dbDriver->pQuery('SELECT featureid, collection, title FROM ' . $this->dbDriver->targetSchema . '.catalog_feature WHERE path=$1::ltree', array(
$results = $this->dbDriver->pQuery('SELECT featureid, collection, title FROM ' . $this->dbDriver->targetSchema . '.catalog_feature WHERE path=$1::ltree ORDER BY created DESC', array(
RestoUtil::path2ltree($catalogId)
));
} catch (Exception $e) {
Expand Down Expand Up @@ -585,8 +585,8 @@ private function storeCatalog($catalog, $user, $context, $collectionId, $feature
$properties = null;
if ( isset($catalog['rtype']) && $catalog['rtype'] === 'collection' ) {
$catalog = array_merge($catalog, [
'title' => null,
'description' => null,
/*'title' => null,
'description' => null,*/
'rtype' => 'collection'
]);
}
Expand Down
67 changes: 65 additions & 2 deletions app/resto/core/utils/AntiMeridian.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ private function fixPolygon(
bool $fix_winding = true,
bool $great_circle = true
): Polygon|MultiPolygon {


if ($force_north_pole || $force_south_pole) {
$fix_winding = false;
Expand All @@ -191,7 +191,7 @@ private function fixPolygon(

if (count($polygons) === 1) {
$polygon = $polygons[0];
if (!Polygon::isClockwise($polygon->getExteriorRing())) {
if (Polygon::isCCW($polygon->getExteriorRing())) {
return $polygon;
} else {
$polygon->setInteriorRings($polygon->getExteriorRing());
Expand Down Expand Up @@ -366,10 +366,53 @@ function ($coord) {
}


/**
* Segment a set of coordinates at the antimeridian.
*
* [IMPORTANT] This function differs from the original implementation in that it first test if the
* polygon split or not the antimeridian based on the bbox order. To do so, it takes the asumption that
* the first coordinates is the most western point of the polygon. From this it computes the bbox and applies
* the GeoJSON rule on antimeridian crossing (https://datatracker.ietf.org/doc/html/rfc7946#section-5.2)
*
* "Consider a set of point Features within the Fiji archipelago,
* straddling the antimeridian between 16 degrees S and 20 degrees S.
* The southwest corner of the box containing these Features is at 20
* degrees S and 177 degrees E, and the northwest corner is at 16
* degrees S and 178 degrees W. The antimeridian-spanning GeoJSON
* bounding box for this FeatureCollection is
*
* "bbox": [177.0, -20.0, -178.0, -16.0]
*
* and covers 5 degrees of longitude.
*
* The complementary bounding box for the same latitude band, not
* crossing the antimeridian, is
*
* "bbox": [-178.0, -20.0, 177.0, -16.0]
*
* and covers 355 degrees of longitude.
*
* The latitude of the northeast corner is always greater than the
* latitude of the southwest corner, but bounding boxes that cross the
* antimeridian have a northeast corner longitude that is less than the
* longitude of the southwest corner."
*
*
* @param array $coords The coordinates to segment.
* @param bool $greatCircle Whether to use great circle calculations.
*/
private function segment(array $coords, bool $greatCircle): array {
$segment = [];
$segments = [];

$westernCoords = $coords[0];
$easternCoords = $this->getEasternmostCoordinate($coords);

if ($westernCoords[0] < $easternCoords[0]) {
// No antimeridian crossing
return [];
}

for ($i = 0; $i < count($coords) - 1; $i++) {
$start = $coords[$i];
$end = $coords[$i + 1];
Expand Down Expand Up @@ -403,6 +446,26 @@ private function segment(array $coords, bool $greatCircle): array {
return $segments;
}

/**
* Returns the easternmost coordinate in an array of coordinates.
*
* @param array $coordinates The array of coordinates.
* @return array|null The easternmost coordinate.
*/
private function getEasternmostCoordinate($coordinates) {
if (empty($coordinates)) {
return null; // Return null if the array is empty
}

// Sort the array by longitude in descending order
usort($coordinates, function($a, $b) {
return $b[0] <=> $a[0]; // Compare longitude values
});

// Return the first coordinate (easternmost)
return $coordinates[0];
}

private function buildPolygons(array &$segments): array {
if (empty($segments)) {
return [];
Expand Down
4 changes: 2 additions & 2 deletions app/resto/core/utils/antimeridian/LineString.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ public function correctOrientation(): void {
}

/**
* Check if the LineString is "clockwise".
* Check if the LineString is "counter clockwise".
* LineStrings don't have a defined clockwise or counterclockwise orientation.
* This method is here for consistency, but it will always return false for LineStrings.
*
* @return bool False for LineString, as orientation doesn't apply.
*/
public function isClockwise(): bool {
public function isCCW(): bool {
return false;
}

Expand Down
100 changes: 57 additions & 43 deletions app/resto/core/utils/antimeridian/Polygon.php
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
<?php

class Polygon {
class Polygon
{

private $exteriorRing; // Array of coordinates representing the exterior ring
private $interiorRings = []; // Array of arrays, each representing an interior ring

/**
* Determine if a ring is oriented clockwise.
*
* @param array $ring The ring to check.
* @return bool True if the ring is clockwise, false otherwise.
*/
public static function isClockwise(array $ring): bool {
$sum = 0;
$n = count($ring);

for ($i = 0; $i < $n - 1; $i++) {
$p1 = $ring[$i];
$p2 = $ring[$i + 1];
$sum += ($p2[0] - $p1[0]) * ($p2[1] + $p1[1]);
}

return $sum > 0;
}

public function __construct(array $geoJsonGeometryOrSegment) {

/**
* Determine if a ring is oriented CounterClockWise.
*
* @param array $ring The ring to check.
* @return bool True if the ring is ccw, false otherwise.
*/
public static function isCCW(array $ring): bool
{
$area = 0.0;
$n = count($ring);

for ($i = 0; $i < $n - 1; $i++) {
$p1 = $ring[$i];
$p2 = $ring[$i + 1];
$area += ($p2[0] - $p1[0]) * ($p2[1] + $p1[1]);
}

return $area < 0;
}

public function __construct(array $geoJsonGeometryOrSegment)
{

if (array_is_list($geoJsonGeometryOrSegment)) {
$geoJsonGeometryOrSegment[] = $geoJsonGeometryOrSegment[0];
$this->exteriorRing = $geoJsonGeometryOrSegment;
}
else {
} else {

if (!isset($geoJsonGeometryOrSegment['type']) || $geoJsonGeometryOrSegment['type'] !== 'Polygon') {
throw new Exception('Invalid GeoJSON: Must be of type Polygon');
Expand All @@ -56,45 +58,53 @@ public function __construct(array $geoJsonGeometryOrSegment) {
}
}

public function toGeoJSON(): array {
public function toGeoJSON(): array
{

return [
'type' => $this->getType(),
'coordinates' => $this->getCoordinates()
];
}

public function getExteriorRing(): array {
public function getExteriorRing(): array
{
return $this->exteriorRing;
}

public function getInteriorRings(): array {
public function getInteriorRings(): array
{
return $this->interiorRings;
}

public function setExteriorRing($exteriorRing) {
public function setExteriorRing($exteriorRing)
{
$this->exteriorRing = $exteriorRing;
}

public function setInteriorRings($interiorRings) {
public function setInteriorRings($interiorRings)
{
$this->interiorRings = $interiorRings;
}

private function isValidRing(array $ring): bool {
private function isValidRing(array $ring): bool
{
return count($ring) >= 4 && $ring[0] === end($ring);
}

public function getCoordinates(): array {
public function getCoordinates(): array
{
$coordinates = [
$this->exteriorRing
];
if ( !empty($this->interiorRings) ) {
if (!empty($this->interiorRings)) {
$coordinates[] = $this->interiorRings;
}
return $coordinates;
}

public function isCoincidentToAntimeridian(): bool {
public function isCoincidentToAntimeridian(): bool
{
if ($this->checkRingCoincidence($this->exteriorRing)) {
return true;
}
Expand All @@ -108,7 +118,8 @@ public function isCoincidentToAntimeridian(): bool {
return false;
}

private function checkRingCoincidence(array $ring): bool {
private function checkRingCoincidence(array $ring): bool
{
for ($i = 0; $i < count($ring) - 1; $i++) {
$start = $ring[$i];
$end = $ring[$i + 1];
Expand All @@ -125,13 +136,14 @@ private function checkRingCoincidence(array $ring): bool {
*
* @return bool True if the exterior ring is CCW and interior rings are CW.
*/
public function checkOrientation(): bool {
if (Polygon::isClockwise($this->exteriorRing)) {
public function checkOrientation(): bool
{
if (!Polygon::isCCW($this->exteriorRing)) {
return false;
}

foreach ($this->interiorRings as $ring) {
if (!Polygon::isClockwise($ring)) {
if (Polygon::isCCW($ring)) {
return false;
}
}
Expand All @@ -143,13 +155,14 @@ public function checkOrientation(): bool {
* Correct the orientation of the rings.
* Ensures the exterior ring is CCW and interior rings are CW.
*/
public function correctOrientation(): void {
if (Polygon::isClockwise($this->exteriorRing)) {
public function correctOrientation(): void
{
if (!Polygon::isCCW($this->exteriorRing)) {
$this->exteriorRing = array_reverse($this->exteriorRing);
}

foreach ($this->interiorRings as &$ring) {
if (!Polygon::isClockwise($ring)) {
if (Polygon::isCCW($ring)) {
$ring = array_reverse($ring);
}
}
Expand Down Expand Up @@ -177,7 +190,8 @@ public function contains($ring): bool
*
* @return string The geometry type.
*/
public function getType(): string {
public function getType(): string
{
return 'Polygon';
}

Expand All @@ -203,12 +217,12 @@ private function isPointInsideExterior(array $point): bool
$yj = $coords[$j][1];

if (($yi > $y) != ($yj > $y) &&
$x < ($xj - $xi) * ($y - $yi) / ($yj - $yi) + $xi) {
$x < ($xj - $xi) * ($y - $yi) / ($yj - $yi) + $xi
) {
$inside = !$inside;
}
}

return $inside;
}

}
Loading

0 comments on commit 2de785e

Please sign in to comment.