Skip to content

Commit

Permalink
WIP Eloquent Stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
ahawlitschek committed Jul 1, 2022
1 parent 26a03a2 commit e8faf99
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 9 deletions.
7 changes: 7 additions & 0 deletions config/postgis.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@

// config for Clickbar/Postgis
use Clickbar\Postgis\IO\Generator\Geojson\GeojsonGenerator;
use Clickbar\Postgis\IO\Generator\WKT\WKTGenerator;

return [

'schema' => 'public',

'eloquent' => [
'default_postgis_type' => 'geography',
'default_srid' => 4326,
],

'json_generator' => GeojsonGenerator::class,

'insert_generator' => \Clickbar\Postgis\IO\Generator\WKB\WKBGenerator::class,

'string_generator' => \Clickbar\Postgis\IO\Generator\Geojson\GeojsonGenerator::class,

];
90 changes: 89 additions & 1 deletion src/Eloquent/HasPostgisColumns.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
namespace Clickbar\Postgis\Eloquent;

use Clickbar\Postgis\Exception\PostgisColumnsNotDefinedException;
use Clickbar\Postgis\Geometries\Geometry;
use Clickbar\Postgis\Geometries\GeometryCollection;
use Clickbar\Postgis\Geometries\GeometryFactory;
use Clickbar\Postgis\IO\Generator\BaseGenerator;

use Clickbar\Postgis\IO\Parser\WKB\WKBParser;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Arr;

trait HasPostgisColumns
Expand All @@ -12,7 +19,7 @@ public function getPostgisTypeAndSrid(string $key)
$this->assertKeyIsInPostgisColumns($key);

$default = [
'geomtype' => config('postgis.eloquent.default_postgis_type'),
'type' => config('postgis.eloquent.default_postgis_type'),
'srid' => config('postgis.eloquent.default_srid'),
];

Expand All @@ -32,6 +39,87 @@ public function getPostgisColumnNames()
}, array_keys($this->postgisColumns));
}

private function getGenerator(): BaseGenerator
{
$generatorClass = config('postgis.insert_generator');

return new $generatorClass();
}

protected function geomFromText(Geometry $geometry, $srid = 4326)
{
$generator = $this->getGenerator();
$geometrySql = $generator->toPostgisGeometrySql($geometry, config('postgis.schema', 'public'));
if ($geometry->hasSrid() && $geometry->getSrid() != $srid) {
$geometrySql = 'ST_TRANSFORM(' . $geometrySql . ', ' . $srid . ')';
}

return $this->getConnection()->raw($geometrySql);
}

protected function geogFromText(Geometry $geometry, $srid = 4326)
{
$generator = $this->getGenerator();
$geometrySql = $generator->toPostgisGeographySql($geometry, config('postgis.schema', 'public'));

if ($geometry->hasSrid() && $geometry->getSrid() != $srid) {
$geometrySql = 'ST_TRANSFORM(' . $geometrySql . ', ' . $srid . ')';
}

return $this->getConnection()->raw($geometrySql);
}

public function getGeometryAsInsertable(Geometry $geometry, array $columnConfig)
{
return match (strtoupper($columnConfig['type'])) {
'GEOMETRY' => $this->geomFromText($geometry, $columnConfig['srid']),
default => $this->geogFromText($geometry, $columnConfig['srid']),
};
}

protected function performInsert(EloquentBuilder $query, array $options = [])
{
$geometryCache = [];

foreach ($this->attributes as $key => $value) {
if ($value instanceof Geometry) {
$geometryCache[$key] = $value; //Preserve the geometry objects prior to the insert
if ($value instanceof GeometryCollection) {
// --> Only insertable into geometry column types
$this->attributes[$key] = $this->geomFromText($value);
} else {
$this->attributes[$key] = $this->geomFromText($value);
$columnConfig = $this->getPostgisTypeAndSrid($key);
$this->attributes[$key] = $this->getGeometryAsInsertable($value, $columnConfig);
}
}
}

$insert = parent::performInsert($query, $options);

foreach ($geometryCache as $key => $value) {
$this->attributes[$key] = $value; //Retrieve the geometry objects so they can be used in the model
}

return $insert; //Return the result of the parent insert
}

public function setRawAttributes(array $attributes, $sync = false)
{
$pgfields = $this->getPostgisColumnNames();

// postgis always returns the geometry as a WKB string, so we need to convert it to a Geometry object
$parser = new WKBParser(new GeometryFactory());

foreach ($attributes as $key => &$value) {
if (in_array($key, $pgfields) && is_string($value)) {
$value = $parser->parse($value);
}
}

return parent::setRawAttributes($attributes, $sync);
}

protected function assertPostgisColumnsNotEmpty()
{
if (! property_exists($this, 'postgisColumns')) {
Expand Down
20 changes: 19 additions & 1 deletion src/Geometries/Geometry.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Clickbar\Postgis\IO\Dimension;
use JsonSerializable;

abstract class Geometry implements GeometryInterface, JsonSerializable
abstract class Geometry implements GeometryInterface, JsonSerializable, \Stringable
{
public function __construct(
protected Dimension $dimension,
Expand All @@ -29,11 +29,29 @@ public function getSrid(): ?int
return $this->srid;
}

public function hasSrid(): bool
{
return $this->srid !== null && $this->srid !== 0;
}

public function jsonSerialize(): mixed
{
$generatorClass = config('postgis.json_generator');
$generator = new $generatorClass();

return json_encode($generator->generate($this));
}

public function __toString(): string
{
$generatorClass = config('postgis.string_generator');
$generator = new $generatorClass();

$generated = $generator->generate($this);
if (! is_string($generated)) {
return json_encode($generated);
}

return $generated;
}
}
4 changes: 4 additions & 0 deletions src/IO/Generator/BaseGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,8 @@ abstract public function generateMultiPolygon(MultiPolygon $multiPolygon): mixed
abstract public function generateMultiPoint(MultiPoint $multiPoint): mixed;

abstract public function generateGeometryCollection(GeometryCollection $geometryCollection): mixed;

abstract public function toPostgisGeometrySql(Geometry $geometry, string $schema): mixed;

abstract public function toPostgisGeographySql(Geometry $geometry, string $schema): mixed;
}
10 changes: 10 additions & 0 deletions src/IO/Generator/Geojson/GeojsonGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,14 @@ public function generateGeometryCollection(GeometryCollection $geometryCollectio
'geometries' => array_map(fn (Geometry $geometry) => $this->generate($geometry), $geometryCollection->getGeometries()),
];
}

public function toPostgisGeometrySql(Geometry $geometry, string $schema): mixed
{
return sprintf("%s.st_geomfromgeojson('%s')", $schema, json_encode($this->generate($geometry)));
}

public function toPostgisGeographySql(Geometry $geometry, string $schema): mixed
{
return sprintf("%s.st_geomfromgeojson('%s')::geography", $schema, json_encode($this->generate($geometry)));
}
}
10 changes: 10 additions & 0 deletions src/IO/Generator/WKB/WKBGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,14 @@ public function generateGeometryCollection(GeometryCollection $geometryCollectio

return $this->byteStringBuilder->toByteString(true);
}

public function toPostgisGeometrySql(Geometry $geometry, string $schema): mixed
{
return sprintf("'%s'::geometry", $this->generate($geometry));
}

public function toPostgisGeographySql(Geometry $geometry, string $schema): mixed
{
return sprintf("'%s'::geography", $this->generate($geometry));
}
}
23 changes: 22 additions & 1 deletion src/IO/Generator/WKT/WKTGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ private function apply3dIfNeeded(string $type, Geometry $geometry): string
return $type;
}

public function generate(Geometry $geometry)
{
$wktWithoutSrid = parent::generate($geometry);

if (! $geometry->hasSrid()) {
return $wktWithoutSrid;
}

return sprintf('SRID=%d;%s', $geometry->getSrid(), $wktWithoutSrid);
}

public function generatePoint(Point $point): mixed
{
$wktType = $this->apply3dIfNeeded('POINT', $point);
Expand Down Expand Up @@ -105,11 +116,21 @@ public function generateGeometryCollection(GeometryCollection $geometryCollectio
{
$geometryWktStrings = implode(',', array_map(
function (Geometry $geometry) {
return $this->generate($geometry);
return parent::generate($geometry);
},
$geometryCollection->getGeometries()
));

return sprintf('GEOMETRYCOLLECTION(%s)', $geometryWktStrings);
}

public function toPostgisGeometrySql(Geometry $geometry, string $schema): mixed
{
return sprintf("%s.ST_GeomFromEWKT('%s')", $schema, $this->generate($geometry));
}

public function toPostgisGeographySql(Geometry $geometry, string $schema): mixed
{
return sprintf("%s.ST_GeogFromText('%s')", $schema, $this->generate($geometry));
}
}
3 changes: 3 additions & 0 deletions src/IO/Parser/WKB/WKBParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public function parse($input): Geometry
{
$this->scanner = new Scanner($input);

$this->dimension = null;
$this->srid = null;

return $this->parseWkbSegment();
}

Expand Down
6 changes: 4 additions & 2 deletions src/Schema/Blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,11 @@ public function geometry($column, int|null $srid = 4326, string $postgisType = '
* @param bool $typmod
* @return Fluent
*/
public function geometrycollection($column, $srid = null, $dimensions = 2, $typmod = true)
public function geometrycollection($column, $srid = null)
{
return $this->addCommand('geometrycollection', compact('column', 'srid', 'dimensions', 'typmod'));
$postgisType = 'GEOMETRY';

return $this->addColumn('geometrycollection', $column, compact('postgisType', 'srid'));
}

/**
Expand Down
28 changes: 24 additions & 4 deletions src/Schema/Grammars/PostgisGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Clickbar\Postgis\Schema\Grammars;

use Clickbar\Postgis\Exception\UnsupportedPostgisTypeException;
use Clickbar\Postgis\Schema\Blueprint;
use Illuminate\Database\Schema\Grammars\PostgresGrammar;
use Illuminate\Support\Fluent;

Expand Down Expand Up @@ -62,18 +63,37 @@ public function typeMultilinestring(Fluent $column): string

public function typeGeography(Fluent $column): string
{
return 'GEOGRAPHY';
return $this->createTypeDefinition($column, 'GEOGRAPHY');
}

public function typeGeometry(Fluent $column): string
{
return 'GEOMETRY';
return $this->createTypeDefinition($column, 'GEOMETRY');
}

public function typeGeometryCollection(Fluent $column): string
{
return $this->createTypeDefinition($column, 'GEOMETRYCOLLECTION');
}

/*
* COMPILE Statements
*/

/**
* Adds a statement to add a geometrycollection geometry column
*
* @param Blueprint $blueprint
* @param Fluent $command
* @return string
*/
public function compileGeometrycollection(Blueprint $blueprint, Fluent $command)
{
$command->type = 'GEOMETRYCOLLECTION';

return $this->compileGeometry($blueprint, $command);
}

/**
* Adds a statement to create the postgis extension
*
Expand Down Expand Up @@ -131,7 +151,7 @@ protected function assertValidPostgisType(Fluent $column)
throw new UnsupportedPostgisTypeException("Postgis type '$column->postgisType' is not a valid postgis type. Valid types are $implodedValidTypes");
}

if (! filter_var($column->srid, FILTER_VALIDATE_INT)) {
if (filter_var($column->srid, FILTER_VALIDATE_INT) === false) {
throw new UnsupportedPostgisTypeException("The given SRID '$column->srid' is not valid. Only integers are allowed");
}

Expand All @@ -144,7 +164,7 @@ private function createTypeDefinition(Fluent $column, $geometryType): string
{
$this->assertValidPostgisType($column);

$schema = config('postgis.schema', 'public'); // TODO: Add config
$schema = config('postgis.schema', 'public');
$type = strtoupper($column->postgisType);

return $schema . '.' . $type . '(' . $geometryType . ', ' . $column->srid . ')';
Expand Down

0 comments on commit e8faf99

Please sign in to comment.