Skip to content

Instantly share code, notes, and snippets.

@Gummibeer
Created June 19, 2024 14:38
Show Gist options
  • Save Gummibeer/367a3463c5b022fcb1839966892abf10 to your computer and use it in GitHub Desktop.
Save Gummibeer/367a3463c5b022fcb1839966892abf10 to your computer and use it in GitHub Desktop.
<?php
namespace App\Geotools\Data;
use App\Geotools\Casts\GeographyCast;
use App\Geotools\Contracts\GeoJsonable;
use App\Geotools\Contracts\WellKnownTextable;
use App\Geotools\GeoJson\GeoJson;
use App\Geotools\GeoJson\Point;
use App\Geotools\Value\WktString;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use League\Geotools\Coordinate\Coordinate as LeagueCoordinate;
use League\Geotools\Coordinate\CoordinateInterface;
/**
* @readonly
*/
class Coordinate extends LeagueCoordinate implements Castable, GeoJsonable, WellKnownTextable
{
public static function fromLatLng(float $latitude, float $longitude): static
{
return new static([$latitude, $longitude]);
}
public static function instance(CoordinateInterface $coordinate): static
{
return static::fromLatLng($coordinate->getLatitude(), $coordinate->getLongitude());
}
public static function castUsing(array $arguments): CastsAttributes
{
return new GeographyCast();
}
public function toWkt(): WktString
{
return new WktString("POINT({$this->getLongitude()} {$this->getLatitude()})");
}
public function toGeoJson(): GeoJson
{
return new Point(
latitude: $this->getLatitude(),
longitude: $this->getLongitude(),
);
}
}
<?php
namespace App\Geotools\Casts;
use App\Geotools\Contracts\WellKnownTextable;
use App\Geotools\Data\Coordinate;
use GeoIO\WKB\Parser\Parser;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Expression;
use InvalidArgumentException;
class GeographyCast implements CastsAttributes
{
public function get(Model $model, string $key, mixed $value, array $attributes): ?Coordinate
{
if ($value === null) {
return null;
}
if (is_string($value)) {
return app(Parser::class)->parse($value);
}
throw new InvalidArgumentException();
}
public function set(Model $model, string $key, mixed $value, array $attributes): ?Expression
{
if ($value === null) {
return null;
}
if ($value instanceof WellKnownTextable) {
return $model->getConnection()->raw("ST_GeogFromText('{$value->toWkt()}')");
}
throw new InvalidArgumentException();
}
}
<?php
namespace App\Geotools;
use App\Geotools\Data\Coordinate;
use BadMethodCallException;
use GeoIO\Factory;
use InvalidArgumentException;
class GeographyFactory implements Factory
{
public function createPoint($dimension, array $coordinates, $srid = null): Coordinate
{
if ($dimension !== '2D') {
throw new InvalidArgumentException();
}
return Coordinate::fromLatLng(
latitude: $coordinates['y'],
longitude: $coordinates['x'],
);
}
public function createLineString($dimension, array $points, $srid = null)
{
throw new BadMethodCallException();
}
public function createLinearRing($dimension, array $points, $srid = null)
{
throw new BadMethodCallException();
}
public function createPolygon($dimension, array $lineStrings, $srid = null)
{
throw new BadMethodCallException();
}
public function createMultiPoint($dimension, array $points, $srid = null)
{
throw new BadMethodCallException();
}
public function createMultiLineString($dimension, array $lineStrings, $srid = null)
{
throw new BadMethodCallException();
}
public function createMultiPolygon($dimension, array $polygons, $srid = null)
{
throw new BadMethodCallException();
}
public function createGeometryCollection($dimension, array $geometries, $srid = null)
{
throw new BadMethodCallException();
}
}
<?php
namespace App\Geotools\GeoJson;
use App\Geotools\Enums\GeoJsonType;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use JsonSerializable;
abstract readonly class GeoJson implements Arrayable, Jsonable, JsonSerializable
{
public function __construct(
public GeoJsonType $type,
) {
}
public function toArray(): array
{
return [
'type' => $this->type->value,
];
}
public function toJson($options = 0): string
{
return json_encode($this, JSON_THROW_ON_ERROR | $options);
}
public function jsonSerialize(): array
{
return $this->toArray();
}
}
<?php
namespace App\Geotools\Contracts;
use App\Geotools\GeoJson\GeoJson;
interface GeoJsonable
{
public function toGeoJson(): GeoJson;
}
<?php
namespace App\Geotools\Enums;
enum GeoJsonType: string
{
case LineString = 'LineString';
case MultiLineString = 'MultiLineString';
case MultiPoint = 'MultiPoint';
case MultiPolygon = 'MultiPolygon';
case Point = 'Point';
case Polygon = 'Polygon';
case Feature = 'Feature';
case FeatureCollection = 'FeatureCollection';
case GeometryCollection = 'GeometryCollection';
}
<?php
namespace App\Geotools;
use GeoIO\Factory;
use GeoIO\WKB\Parser\Parser;
use Illuminate\Support\ServiceProvider;
class GeotoolsServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->scoped(Factory::class, GeographyFactory::class);
$this->app->scoped(Parser::class);
}
}
<?php
namespace App\Geotools\GeoJson;
use App\Geotools\Enums\GeoJsonType;
readonly class Point extends GeoJson
{
public function __construct(
public float $latitude,
public float $longitude,
) {
parent::__construct(GeoJsonType::Point);
}
public function toArray(): array
{
return array_merge([
'coordinates' => [
$this->longitude,
$this->latitude,
],
], parent::toArray());
}
}
<?php
namespace App\Geotools\Enums;
enum PostgisType: string
{
case Geometry = 'geometry';
case Geography = 'geography';
}
<?php
namespace App\Geotools\Contracts;
use App\Geotools\Value\WktString;
interface WellKnownTextable
{
public function toWkt(): WktString;
}
<?php
namespace App\Geotools\Value;
use Stringable;
readonly class WktString implements Stringable
{
public function __construct(public string $value)
{
}
public function __toString(): string
{
return $this->value;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment