Skip to content

Commit

Permalink
Initial commit after moving to Yii2 extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
Stefano Mtangoo committed Oct 10, 2024
0 parents commit 99c9a07
Show file tree
Hide file tree
Showing 10 changed files with 1,098 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# composer vendor dir
/vendor

# phpstorm project files
.idea

# netbeans project files
nbproject

# zend studio for eclipse project files
.buildpath
.project
.settings

# windows thumbnail cache
Thumbs.db

# Mac DS_Store Files
.DS_Store
278 changes: 278 additions & 0 deletions Behaviors/GeometryBehavior.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
<?php

namespace Yii2\Extension\Postgis\Behaviors;

use Yii2\Extension\Postgis\Helpers\GeoJsonHelper;
use yii\base\Behavior;
use yii\base\InvalidConfigException;
use yii\db\ActiveRecord;
use yii\db\Connection;
use yii\db\Expression;
use yii\db\Query;
use yii\di\Instance;

/**
* Class PostgisBehavior
* Handles model attribute stored in postgis format (via GeoJson)
* @property-read array geometryNames available geometry names
* @package Yii2\Extension\Postgis
* @author Chernyavsky Denis <[email protected]>
*/
class GeometryBehavior extends Behavior
{

/**
* Geometry names
*/
const GEOMETRY_POINT = 'Point';
const GEOMETRY_MULTIPOINT = 'MultiPoint';
const GEOMETRY_LINESTRING = 'LineString';
const GEOMETRY_MULTILINESTRING = 'MultiLineString';
const GEOMETRY_POLYGON = 'Polygon';
const GEOMETRY_MULTIPOLYGON = 'MultiPolygon';

/**
* @var string|array|callable|Connection Db connection for PostgreSQL database with installed Postgis extension
* to convert model attribute from Postgis binary to GeoJson.
* Will be used as fallback if [[owner]] does not provide db connection via [[getDb()]] method.
* You can configure this connection explicitly in [[behaviors()]]
* section or via container definitions in your app config.
*/
public $db;

/**
* @var string attribute name that will be automatically handled
*/
public $attribute;

/**
* @var string geometry name
*/
public $type;

/**
* @var string srid number
*/
public $srid;

/**
/**
* @var bool don't convert attribute afterFind if it in Postgis binary format (it requires a separate query)
*/
public $skipAfterFindPostgis = false;

/**
* @var array stored coordinates for afterSave
*/
protected $_coordinates;

/**
* @inheritdoc
*/
public function events()
{

$events = [
ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
ActiveRecord::EVENT_AFTER_INSERT => 'afterSave',
ActiveRecord::EVENT_AFTER_UPDATE => 'afterSave',
ActiveRecord::EVENT_AFTER_FIND => 'afterFind',
];

return $events;
}

/**
* @inheritdoc
* @throws InvalidConfigException
*/
public function init()
{

if (empty($this->attribute)) {
throw new InvalidConfigException("Class property 'attribute' does`t set");
}

if (empty($this->type)) {
throw new InvalidConfigException("Class property 'geometry' does`t set");
}

if (!in_array($this->type, $this->geometryNames)) {
throw new InvalidConfigException('Unknow geometry type');
}

parent::init();
}

/**
* Convert array to GeoJson expression before save
* @return bool
*/
public function beforeSave()
{

$attributeChanged = $this->owner->isAttributeChanged($this->attribute);

// store coordinates for afterSave;
$this->_coordinates = $this->owner->{$this->attribute};

$this->coordinatesToGeoJson();

if (!$attributeChanged) {
$this->owner->setOldAttribute($this->attribute, $this->owner->{$this->attribute});
}

return true;
}

/**
* Convert attribute to array after save
* @return bool
*/
public function afterSave()
{

$this->owner->{$this->attribute} = $this->_coordinates;

$this->owner->setOldAttribute($this->attribute, $this->owner->{$this->attribute});

return true;
}

/**
* Convert attribute to array after find
*
* @return bool
* @throws InvalidConfigException
* @throws \yii\db\Exception
*/
public function afterFind()
{

if (!is_object(json_decode($this->owner->{$this->attribute}))) {

if ($this->skipAfterFindPostgis) {
return true;
} else {
$this->postgisToGeoJson();
}
}

$this->geoJsonToCoordinates();

$this->owner->setOldAttribute($this->attribute, $this->owner->{$this->attribute});

return true;
}

/**
* Return available geometry names
* @return array
*/
public function getGeometryNames()
{
return [
self::GEOMETRY_POINT,
self::GEOMETRY_MULTIPOINT,
self::GEOMETRY_LINESTRING,
self::GEOMETRY_MULTILINESTRING,
self::GEOMETRY_POLYGON,
self::GEOMETRY_MULTIPOLYGON,
];
}

/**
* Convert model attribute from array to GeoJson insert expression
* @return Expression
*/
protected function coordinatesToGeoJson()
{

$coordinates = $this->owner->{$this->attribute};

if (!empty($coordinates)) {

$query = is_array($coordinates) ? GeoJsonHelper::toGeometry($this->type, $coordinates, $this->srid) : "'$coordinates'";

$this->owner->{$this->attribute} = new Expression($query);
} else {
$this->owner->{$this->attribute} = null;
}
}

/**
* Convert model attribute from GeoJson to array
* @return array
*/
protected function geoJsonToCoordinates()
{
if (!empty($this->owner->{$this->attribute})) {
$this->owner->{$this->attribute} = GeoJsonHelper::toArray($this->owner->{$this->attribute});
}
}

/**
* Convert model attribute from Postgis binary to GeoJson
*
* @throws InvalidConfigException
* @throws \yii\db\Exception
*/
protected function postgisToGeoJson()
{
$attribute = $this->attribute;

if (!empty($this->owner->$attribute)) {
$db = $this->_getDb();
$query = new Query();
$res = $query->select("ST_asGeoJson('" . $this->owner->$attribute . "') as $attribute")->createCommand($db)->queryOne();
$geoJson = $res[$attribute];

$this->owner->$attribute = $geoJson;
}
}

/**
* @var Connection
*/
private $_db;

/**
* @var Connection
*/
private $_dbInitialized = false;

/**
* @return Connection|null
* @throws InvalidConfigException
*/
private function _getDb()
{
if (empty($this->_db) && !$this->_dbInitialized) {
if ($this->db !== null) {
// if db connection is set explicitly, use it
$db = Instance::ensure($this->db, Connection::class);
} elseif (method_exists($this->owner, 'getDb') && ($db = $this->owner->getDb()) !== null) {
// Do not use $this->owner->canGetProperty('db') as $this->owner->db,
// since owner can have db attribute, which is not component name
// and owner may be not an ActiveRecord instance

// ActiveRecord default getDb() returns already created Connection object,
// but there is may be a name, config or lambda which we also want to respect
if ((is_string($db) || is_array($db) || is_callable($db)) && !$db instanceof Connection) {
$db = Instance::ensure($db, Connection::class);
}
} else {
// do not execute the following code, since db component may change during app lifecycle and
// we want to respect such behavior
// $db = Yii::$app->getDb()
$db = null;
}

$this->_db = $db;
$this->_dbInitialized = true;
}

return $this->_db;
}
}
78 changes: 78 additions & 0 deletions Behaviors/StBufferBehavior.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace Yii2\Extension\Postgis\Behaviors;

use yii\base\Behavior;
use yii\db\ActiveRecord;
use Yii2\Extension\Postgis\components\StBuffer;

/**
* Class StBufferBehavior
* Behavior for saving buffer based on geometry and radius model attributes
* @package Yii2\Extension\Postgis
* @author Chernyavsky Denis <[email protected]>
*/
class StBufferBehavior extends Behavior
{
/**
* @var string attribute name for saving ST_Buffer
*/
public $attribute;

/**
* @var string geometry attribute name
*/
public $attributeGeometry;

/**
* @var string radius attribute name
*/
public $attributeRadius;

/**
* @var bool use Geography instead Geometry
*/
public $geography = false;

/**
* @var string radius units
*/
public $radiusUnit;

/**
* @var array options for ST_Buffer
*/
public $options = [];

/**
* @inheritdoc
*/
public function events()
{

return [
ActiveRecord::EVENT_BEFORE_INSERT => 'setGeometry',
ActiveRecord::EVENT_BEFORE_UPDATE => 'setGeometry',
];
}

/**
* Generate expression for saving ST_Buffer
*/
public function setGeometry()
{
$model = $this->owner;

if ($model->isAttributeChanged($this->attributeRadius, false) or $model->isAttributeChanged($this->attributeGeometry, false)) {

$stBuffer = \Yii::createObject([
'class' => StBuffer::className(),
'geography' => $this->geography,
'radiusUnit' => $this->radiusUnit,
'options' => $this->options,
]);

$model->{$this->attribute} = $stBuffer->getBuffer($model->{$this->attributeGeometry}, $model->{$this->attributeRadius});
}
}
}
Loading

0 comments on commit 99c9a07

Please sign in to comment.