diff --git a/README.md b/README.md
index f9bbdbc..c6433ad 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,8 @@
![Simple Map](resources/banner.jpg)
# Simple Map
-A beautifully simple Google Map field type for Craft CMS. Full localization support, and compatible with Matrix.
+A beautifully simple Google Map field type for Craft CMS. Full localization support, compatible with Matrix, supports
+searching by location and sorting by distance.
## Installation
Clone this repo into `craft/plugins/simplemap`.
@@ -15,15 +16,32 @@ The field type will return an array containing `lat`, `lng`, `zoom`, `address`,
This contains the locations address, broken down into its constituent parts. All values are optional so you'll need to have checks on any you use to make sure they exist.
A list of the available values can be found [here](https://developers.google.com/maps/documentation/geocoding/intro#Types).
+**Searching and Sorting**
+
+You can search for elements using the location specified in your map field. When searching by your map field you also have the option to sort the results by distance.
+
+```twig
+{% set entries = craft.entries.myMapField({
+ location: 'Maidstone, Kent, UK',
+ radius: 100,
+ unit: 'mi'
+}).order('distance') %}
+```
+
+- `location`: Can either be an address string (requires a Google Maps Geocoding API key) or a Lat Lng Array (`{ 'lat': 51.27219908, 'lng': 0.51545620 }` or `craft.simpleMap.latLng(51.27219908, 0.51545620)`).
+- `radius`: The radius around the location to search. Defaults to `50`.
+- `unit`: The unit of measurement for the search. Can be either `km` (kilometers) or `mi` (miles). Defaults to `km`.
+
![How it looks](resources/preview.png)
## Changelog
-### 1.2.0 [DEV - Work In Progress]
-- Added optional Google API Server Key setting
+### 1.2.0
- **Added search and sorting support.**
+- Added optional Google API Server Key setting
+- **_WARNING:_** This update will break any map fields that are NOT standalone (global) or in a Matrix field.
-### 1.1.2 [RELEASE]
+### 1.1.2
- Fix \#5 via @jripmeester - Fixed Lat / Lng being populated with function, not number.
### 1.1.1
diff --git a/SimpleMapPlugin.php b/SimpleMapPlugin.php
index 285f941..d94d753 100644
--- a/SimpleMapPlugin.php
+++ b/SimpleMapPlugin.php
@@ -24,12 +24,12 @@ public function getDescription()
public function getVersion()
{
- return '1.1.3';
+ return '1.2.0';
}
public function getSchemaVersion()
{
- return '0.0.2';
+ return '0.0.5';
}
public function getDeveloper()
@@ -66,4 +66,4 @@ public function getSettingsHtml()
));
}
-}
\ No newline at end of file
+}
diff --git a/fieldtypes/SimpleMap_MapFieldType.php b/fieldtypes/SimpleMap_MapFieldType.php
index ee207d2..d4760a9 100644
--- a/fieldtypes/SimpleMap_MapFieldType.php
+++ b/fieldtypes/SimpleMap_MapFieldType.php
@@ -70,6 +70,8 @@ public function modifyElementsQuery(DbCommand $query, $value)
{
if ($value !== null)
craft()->simpleMap->modifyQuery($query, $value);
+
+ return $query;
}
}
\ No newline at end of file
diff --git a/migrations/m160606_162300_simpleMap_updateFieldStorage.php b/migrations/m160606_162300_simpleMap_updateFieldStorage.php
new file mode 100644
index 0000000..fc85ae5
--- /dev/null
+++ b/migrations/m160606_162300_simpleMap_updateFieldStorage.php
@@ -0,0 +1,59 @@
+db->tableExists('simplemap_maps'))
+ craft()->plugins->getPlugin('simpleMap')->createTables();
+
+ $fields = craft()->fields->getAllFields();
+
+ foreach ($fields as $field)
+ {
+ if ($field->type === 'SimpleMap_Map')
+ $this->_transferData($field, 'content');
+
+ if ($field->type === 'Matrix')
+ foreach (craft()->matrix->getBlockTypesByFieldId($field->id) as $blockType)
+ foreach ($blockType->getFields() as $blockTypeField)
+ if ($blockTypeField->type == 'SimpleMap_Map')
+ $this->_transferData($blockTypeField, 'matrixcontent_' . $field->handle, $blockType->handle . '_' . $blockTypeField->handle);
+ }
+
+ return true;
+ }
+
+ private function _transferData(FieldModel $field, $tableName, $fieldHandle = "")
+ {
+ if (!$fieldHandle)
+ $fieldHandle = $field->handle;
+
+ $tableData = craft()->db->createCommand()
+ ->select('elementId, locale, field_' . $fieldHandle)
+ ->from($tableName)
+ ->where('field_' . $fieldHandle . ' IS NOT NULL')
+ ->queryAll();
+
+ foreach ($tableData as $row) {
+ $record = new SimpleMap_MapRecord;
+ $record->ownerId = $row['elementId'];
+ $record->fieldId = $field->id;
+ $record->ownerLocale = $row['locale'];
+
+ $f = json_decode($row['field_' . $fieldHandle], true);
+
+ $record->setAttribute('lat', $f['lat']);
+ $record->setAttribute('lng', $f['lng']);
+ $record->setAttribute('zoom', $f['zoom']);
+ $record->setAttribute('address', $f['address']);
+ if ($f !== null && array_key_exists('parts', $f))
+ $record->setAttribute('parts', json_encode($f['parts']));
+
+ $record->save();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/records/SimpleMap_MapRecord.php b/records/SimpleMap_MapRecord.php
index 0f7db8c..2e46a49 100644
--- a/records/SimpleMap_MapRecord.php
+++ b/records/SimpleMap_MapRecord.php
@@ -5,16 +5,16 @@
class SimpleMap_MapRecord extends BaseRecord {
public static $dec = array(AttributeType::Number, 'column' => ColumnType::Decimal, 'length' => 12, 'decimals' => 8);
+ const TABLE_NAME = 'simplemap_maps';
public function getTableName()
{
- return 'simplemap_maps';
+ return static::TABLE_NAME;
}
public function defineRelations()
{
return array(
-// 'element' => array(static::BELONGS_TO, 'ElementRecord', 'id', 'required' => true, 'onDelete' => static::CASCADE),
'owner' => array(static::BELONGS_TO, 'ElementRecord', 'required' => true, 'onDelete' => static::CASCADE),
'ownerLocale' => array(static::BELONGS_TO, 'LocaleRecord', 'ownerLocale', 'onDelete' => static::CASCADE, 'onUpdate' => static::CASCADE),
'field' => array(static::BELONGS_TO, 'FieldRecord', 'required' => true, 'onDelete' => static::CASCADE)
diff --git a/releases.json b/releases.json
index 38901b7..3bd7085 100644
--- a/releases.json
+++ b/releases.json
@@ -50,5 +50,15 @@
"notes": [
"Fix #5 via @jripmeester - Fixed Lat / Lng being populated with function, not number."
]
+ },
+ {
+ "version": "1.2.0",
+ "downloadUrl": "https://github.com/ethercreative/SimpleMap/archive/v1.2.0.zip",
+ "date": "2016-06-07T10:00:00-08:00",
+ "notes": [
+ "**Added search and sorting support.**",
+ "Added optional Google API Server Key setting",
+ "**_WARNING:_** This update will break any map fields that are NOT standalone (global) or in a Matrix field."
+ ]
}
]
\ No newline at end of file
diff --git a/services/SimpleMapService.php b/services/SimpleMapService.php
index e791104..35b42a0 100644
--- a/services/SimpleMapService.php
+++ b/services/SimpleMapService.php
@@ -4,10 +4,32 @@
class SimpleMapService extends BaseApplicationComponent {
+ public $settings;
+
+ public $searchLatLng;
+ public $searchEarthRad;
+
+ /// PUBLIC ///
+
+ /**
+ * Initialize
+ */
+ public function init()
+ {
+ $this->settings = craft()->plugins->getPlugin('SimpleMap')->getSettings();
+ }
+
+ /**
+ * Get Map Field
+ *
+ * @param SimpleMap_MapFieldType $fieldType
+ * @param $value
+ * @return SimpleMap_MapModel
+ */
public function getField (SimpleMap_MapFieldType $fieldType, $value)
{
$owner = $fieldType->element;
- $field = $fieldType->model;
+ $field = $fieldType->model;;
$record = SimpleMap_MapRecord::model()->findByAttributes(array(
'ownerId' => $owner->id,
@@ -28,11 +50,17 @@ public function getField (SimpleMap_MapFieldType $fieldType, $value)
$model = new SimpleMap_MapModel;
}
- $model->distance = null;
+ $model->distance = $this->_calculateDistance($model);
return $model;
}
+ /**
+ * Save Map Field
+ *
+ * @param SimpleMap_MapFieldType $fieldType
+ * @return bool
+ */
public function saveField (SimpleMap_MapFieldType $fieldType)
{
$owner = $fieldType->element;
@@ -47,8 +75,6 @@ public function saveField (SimpleMap_MapFieldType $fieldType)
$data['lat'] = (double)$data['lat'];
$data['lng'] = (double)$data['lng'];
- SimpleMapPlugin::log(print_r(gettype($data['lat']), true));
-
$record = SimpleMap_MapRecord::model()->findByAttributes(array(
'ownerId' => $owner->id,
'fieldId' => $field->id,
@@ -67,15 +93,109 @@ public function saveField (SimpleMap_MapFieldType $fieldType)
$save = $record->save();
if (!$save) {
- SimpleMapPlugin::log(print_r($record->getErrors(), true));
+ SimpleMapPlugin::log(print_r($record->getErrors(), true), LogLevel::Error);
}
return $save;
}
+ /**
+ * Modify Query
+ *
+ * @param DbCommand $query
+ * @param array $params
+ */
public function modifyQuery (DbCommand &$query, $params = array())
{
- //
+ $query->join(SimpleMap_MapRecord::TABLE_NAME, 'elements.id=' . craft()->db->tablePrefix . SimpleMap_MapRecord::TABLE_NAME . '.ownerId');
+
+ if (array_key_exists('location', $params)) {
+ $this->_searchLocation($query, $params);
+ }
+ }
+
+
+ /// PRIVATE ///
+
+ /**
+ * Search for entries by location
+ *
+ * @param DbCommand $query
+ * @param array $params
+ */
+ private function _searchLocation (DbCommand &$query, $params)
+ {
+ $location = $params['location'];
+ $radius = array_key_exists('radius', $params) ? $params['radius'] : 50;
+ $unit = array_key_exists('unit', $params) ? $params['unit'] : 'kilometers';
+
+ if (!is_numeric($radius)) $radius = (float)$radius;
+ if (!is_numeric($radius)) $radius = 50;
+
+ if (!in_array($unit, array('km', 'mi'))) $unit = 'km';
+
+ if (is_string($location)) $location = $this->_getLatLngFromAddress($location);
+ if (is_array($location)) {
+ if (!array_key_exists('lat', $location) || !array_key_exists('lng', $location))
+ $location = null;
+ } else return;
+
+ if ($location === null) return;
+
+ if ($unit === 'km') $earthRad = 6371;
+ else $earthRad = 3959;
+
+ $this->searchLatLng = $location;
+ $this->searchEarthRad = $earthRad;
+
+ $table = craft()->db->tablePrefix . SimpleMap_MapRecord::TABLE_NAME;
+
+ $haversine = "($earthRad * acos(cos(radians($location[lat])) * cos(radians($table.lat)) * cos(radians($table.lng) - radians($location[lng])) + sin(radians($location[lat])) * sin(radians($table.lat))))";
+
+ $query
+ ->addSelect($haversine . ' AS distance')
+ ->having('distance <= ' . $radius);
+ }
+
+ /**
+ * Find lat/lng from string address
+ *
+ * @param $address
+ * @return null|array
+ *
+ * TODO: Cache results?
+ */
+ private function _getLatLngFromAddress ($address)
+ {
+ if (!$this->settings['serverApiKey']) return null;
+
+ $url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' . rawurlencode($address)
+ . '&key=' . $this->settings['serverApiKey'];
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ $resp = json_decode(curl_exec($ch), true);
+
+ if (array_key_exists('error_message', $resp) && $resp['error_message'])
+ SimpleMapPlugin::log($resp['error_message'], LogLevel::Error);
+
+ if (empty($resp['results'])) return null;
+
+ return $resp['results'][0]['geometry']['location'];
+ }
+
+ private function _calculateDistance (SimpleMap_MapModel $model)
+ {
+ if (!$this->searchLatLng || !$this->searchEarthRad) return null;
+
+ $lt1 = $this->searchLatLng['lat'];
+ $ln1 = $this->searchLatLng['lng'];
+
+ $lt2 = $model->lat;
+ $ln2 = $model->lng;
+
+ return ($this->searchEarthRad * acos(cos(deg2rad($lt1)) * cos(deg2rad($lt2)) * cos(deg2rad($ln2) - deg2rad($ln1)) + sin(deg2rad($lt1)) * sin(deg2rad($lt2))));
}
}
\ No newline at end of file
diff --git a/templates/plugin-settings.twig b/templates/plugin-settings.twig
index 33685fc..b98a47a 100644
--- a/templates/plugin-settings.twig
+++ b/templates/plugin-settings.twig
@@ -2,7 +2,7 @@
{{ forms.textField({
label: "Google API Server Key"|t,
- instructions: 'Optional. Used for searching and sorting by textual address / location. If left blank, only searching and sorting by Lat/Lng will be available. Get an API key.',
+ instructions: 'Optional. Used for searching and sorting by textual address / location. If left blank, only searching and sorting by Lat/Lng will be available. Get an API key.',
id: 'serverApiKey',
name: 'serverApiKey',
value: settings.serverApiKey,
diff --git a/variables/SimpleMapVariable.php b/variables/SimpleMapVariable.php
new file mode 100644
index 0000000..a0d1a6f
--- /dev/null
+++ b/variables/SimpleMapVariable.php
@@ -0,0 +1,15 @@
+