/** * @param $from - string|array * string: GeoJson representation of POINT * array: location in the form [ <lng>, <lat> ] (two floats) * @param $attribute - attribute name of POINT * @param $radius number - search radius in kilometers * @return $this - query that returns models that are near to $from * * Example usages: * $here = [4.9, 52.3]; // longitude and latitude of my place * $nearestModel = <model>::find()->nearest($here, <attributeName>, 100)->one(); // search radius is 100 km * $fiveNearestModels = <model>::find()->nearest($here, <attributeName>, 100)->limit(5)->all(); * $dataProvider = new ActiveDataProvider([ 'query' => <model>::find()->nearest($here, <attributeName>, 100) ]); * * * @link http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/ * @link https://en.wikipedia.org/wiki/Haversine_formula */ public function nearest($from, $attribute, $radius = 100) { $lenPerDegree = 111.045; // km per degree latitude; for miles, use 69.0 if (is_string($from)) { $feat = SpatialHelper::jsonToGeom($from); if ($feat && $feat['type'] == 'Point') { $from = $feat['coordinates']; } } if (!is_array($from)) { return $this; } $lng = $from[0]; $lat = $from[1]; $dLat = $radius / $lenPerDegree; $dLng = $dLat / cos(deg2rad($lat)); /** @var \yii\db\ActiveRecord $modelCls */ $modelCls = $this->modelClass; $subQuery = $this->create($this)->from($modelCls::tableName())->select(['*', '_lng' => "X({$attribute})", '_lat' => "Y({$attribute})"])->having(['between', '_lng', $lng - $dLng, $lng + $dLng])->andHaving(['between', '_lat', $lat - $dLat, $lat + $dLat]); $this->from([$subQuery])->select(['*', '_d' => "{$lenPerDegree}*DEGREES(ACOS(COS(RADIANS(:lt))*COS(RADIANS(_lat))*COS(RADIANS(:lg)-RADIANS(_lng))+SIN(RADIANS(:lt))*SIN(RADIANS(_lat))))"])->params([':lg' => $lng, ':lt' => $lat])->having(['<', '_d', $radius])->orderBy(['_d' => SORT_ASC]); $this->where = null; $this->limit = null; $this->offset = null; $this->distinct = null; $this->groupBy = null; $this->join = null; $this->union = null; return $this; }
public function afterFind() { parent::afterFind(); $scheme = static::getTableSchema(); foreach ($scheme->columns as $column) { if (static::isSpatial($column)) { $field = $column->name; $attr = $this->getAttribute($field); // get WKT if ($attr) { if (YII_DEBUG && preg_match('/[\\x80-\\xff]+/', $attr)) { /* If you get an exception here, it probably means you have overridden find() and did not return sjaakp\spatial\ActiveQuery. */ throw new InvalidCallException('Spatial attribute not converted.'); } $geom = SpatialHelper::wktToGeom($attr); // Transform geometry FeatureCollection... if ($geom['type'] == 'GeometryCollection') { $feats = []; foreach ($geom['geometries'] as $g) { $feats[] = ['type' => 'Feature', 'geometry' => $g, 'properties' => $this->featureProperties($field, $g)]; } $feature = ['type' => 'FeatureCollection', 'features' => $feats]; } else { // ... or to Feature $feature = SpatialHelper::geomToFeature($geom, $this->featureProperties($field, $geom)); } $this->setAttribute($field, Json::encode($feature)); } } } }