clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
<?php
namespace Drupal\geofield\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a GeofieldBackend annotation object.
*
* @ingroup geofield_api
*
* @Annotation
*/
class GeofieldBackend extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The administrative label of the geofield backend.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $admin_label = '';
/**
* The description of the geofield backend.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $description = '';
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Drupal\geofield\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a Geofield Proximity Source annotation object.
*
* @see \Drupal\geofield\Plugin\GeofieldProximitySourceManager
* @see plugin_api
*
* @Annotation
*/
class GeofieldProximitySource extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The label of the plugin.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $label;
/**
* A short description of the plugin.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $description;
/**
* A description to show in the exposed form.
*
* @var \Drupal\Core\Annotation\Translation
* (optional)
*
* @ingroup plugin_translatable
*/
public $exposedDescription;
/**
* An array of the view handler plugins type (contexts) it would work for.
*
* Possible values:
* - filter
* - sort
* - field
* - NULL (all)
*
* @var array
* (optional)
*
* @ingroup plugin_translatable
*/
public $context;
/**
* A flag that specify if the plugin should work only if exposed filter.
*
* @var bool
* (optional)
*
* @ingroup plugin_translatable
*/
public $exposedOnly;
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Drupal\geofield;
/**
* Helper class to convert point object from one format to the other.
*/
class DmsConverter implements DmsConverterInterface {
/**
* {@inheritdoc}
*/
public static function dmsToDecimal(DmsPoint $point) {
$lon_data = $point->getLon();
$lat_data = $point->getLat();
$lon = round($lon_data['degrees'] + ($lon_data['minutes'] / 60) + ($lon_data['seconds'] / 3600), 10);
$lat = round($lat_data['degrees'] + ($lat_data['minutes'] / 60) + ($lat_data['seconds'] / 3600), 10);
$lon = ($lon_data['orientation'] == 'W') ? (-1 * $lon) : $lon;
$lat = ($lat_data['orientation'] == 'S') ? (-1 * $lat) : $lat;
return [$lon, $lat];
}
/**
* {@inheritdoc}
*/
public static function decimalToDms($lon, $lat) {
$lat_direction = $lat < 0 ? 'S' : 'N';
$lon_direction = $lon < 0 ? 'W' : 'E';
$lat_in_degrees = floor(abs($lat));
$lon_in_degrees = floor(abs($lon));
$la_decimal = (abs($lat) - $lat_in_degrees) * 60;
$lon_decimal = (abs($lon) - $lon_in_degrees) * 60;
$lat_minutes = floor($la_decimal);
$lon_minutes = floor($lon_decimal);
$la_decimal = ($la_decimal - $lat_minutes) * 60;
$lon_decimal = ($lon_decimal - $lon_minutes) * 60;
$lat_seconds = round($la_decimal);
$lon_seconds = round($lon_decimal);
return new DmsPoint([
'orientation' => $lon_direction,
'degrees' => $lon_in_degrees,
'minutes' => $lon_minutes,
'seconds' => $lon_seconds,
],
[
'orientation' => $lat_direction,
'degrees' => $lat_in_degrees,
'minutes' => $lat_minutes,
'seconds' => $lat_seconds,
]
);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Drupal\geofield;
/**
* Defines an interface for DmsConverter.
*/
interface DmsConverterInterface {
/**
* Transforms a DMS point to a decimal one.
*
* @param \Drupal\geofield\DmsPoint $point
* The DMS Point to transform.
*
* @return array
* The equivalent Decimal Point array.
*/
public static function dmsToDecimal(DmsPoint $point);
/**
* Transforms a Decimal point to a dms one.
*
* @param float $lon
* The Decimal Point to transform longitude.
* @param float $lat
* The Decimal Point to transform latitude.
*
* @return \Drupal\geofield\DmsPoint
* The equivalent DMS Point object.
*/
public static function decimalToDms($lon, $lat);
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Drupal\geofield;
/**
* Helper class to map DMS Point structure.
*/
class DmsPoint {
/**
* The longitude component.
*
* @var array
*/
protected $lon;
/**
* The latitude component.
*
* @var array
*/
protected $lat;
/**
* DmsPoint constructor.
*
* @param array $lon
* The longitude components.
* @param array $lat
* The latitude components.
*/
public function __construct(array $lon, array $lat) {
$this->lat = $lat;
$this->lon = $lon;
}
/**
* Retrieves an object property.
*
* @param string $property
* The property to get.
*
* @return array|null
* The property if exists, otherwise NULL.
*/
public function get($property) {
return $this->{$property} ?? NULL;
}
/**
* Get the Longitude property.
*
* @return array
* The lon components.
*/
public function getLon() {
return $this->lon;
}
/**
* Set the Longitude property.
*
* @param array $lon
* The lon components.
*/
public function setLon(array $lon) {
$this->lon = $lon;
}
/**
* Get the Latitude property.
*
* @return array
* The lat components.
*/
public function getLat() {
return $this->lat;
}
/**
* Set the Latitude property.
*
* @param array $lat
* The lat components.
*/
public function setLat(array $lat) {
$this->lat = $lat;
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Drupal\geofield\Element;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a Geofield bounds form element.
*
* @FormElement("geofield_bounds")
*/
class GeofieldBounds extends GeofieldElementBase {
/**
* {@inheritdoc}
*/
public static function getComponents() {
return [
'top' => [
'title' => t('Top'),
'range' => 90,
],
'right' => [
'title' => t('Right'),
'range' => 180,
],
'bottom' => [
'title' => t('Bottom'),
'range' => 90,
],
'left' => [
'title' => t('Left'),
'range' => 180,
],
];
}
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#process' => [
[$class, 'elementProcess'],
],
'#element_validate' => [
[$class, 'boundsValidate'],
],
'#theme' => 'geofield_bounds',
'#theme_wrappers' => ['fieldset'],
];
}
/**
* Validates a Geofield bounds element.
*
* @param array $element
* The element being processed.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*/
public static function boundsValidate(array &$element, FormStateInterface $form_state, array &$complete_form) {
static::elementValidate($element, $form_state, $complete_form);
$pairs = [
[
'bigger' => 'top',
'smaller' => 'bottom',
],
[
'bigger' => 'right',
'smaller' => 'left',
],
];
foreach ($pairs as $pair) {
if ($element[$pair['smaller']]['#value'] > $element[$pair['bigger']]['#value']) {
$components = static::getComponents();
$form_state->setError(
$element[$pair['smaller']],
t('@title: @component_bigger must be greater than @component_smaller.', [
'@title' => $element['#title'],
'@component_bigger' => $components[$pair['bigger']]['title'],
'@component_smaller' => $components[$pair['smaller']]['title'],
])
);
}
}
}
}

View File

@@ -0,0 +1,182 @@
<?php
namespace Drupal\geofield\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElement;
use Drupal\geofield\DmsConverter;
/**
* Provides a Geofield DMS form element.
*
* @FormElement("geofield_dms")
*/
class GeofieldDms extends FormElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#process' => [
[$class, 'dmsProcess'],
],
'#element_validate' => [
[$class, 'elementValidate'],
],
'#theme_wrappers' => ['fieldset'],
];
}
/**
* Generates the Geofield DMS form element.
*
* @param array $element
* An associative array containing the properties and children of the
* element. Note that $element must be taken by reference here, so processed
* child elements are taken over into $form_state.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*/
public static function dmsProcess(array &$element, FormStateInterface $form_state, array &$complete_form) {
$element['#tree'] = TRUE;
$element['#input'] = TRUE;
$default_value = [];
if (!empty($element['#default_value']['lon']) && !empty($element['#default_value']['lat'])) {
$default_value = DmsConverter::decimalToDms($element['#default_value']['lon'], $element['#default_value']['lat']);
}
$options = [
'lat' => [
'N' => t('North'),
'S' => t('South'),
],
'lon' => [
'E' => t('East'),
'W' => t('West'),
],
];
foreach ($options as $type => $option) {
$component_default = !empty($default_value) ? $default_value->get($type) : [];
self::processComponent($element, $type, $option, $component_default);
}
unset($element['#value']);
// Set this to false always to prevent notices.
$element['#required'] = FALSE;
return $element;
}
/**
* Validates a Geofield DMS form element.
*
* @param array $element
* The element being processed.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*/
public static function elementValidate(array &$element, FormStateInterface $form_state, array &$complete_form) {
}
/**
* Helper function to generate each coordinate component form element.
*
* @param array $element
* The form element.
* @param string $type
* The component type.
* @param array $options
* The component options.
* @param array $default_value
* The component default value array.
*/
protected static function processComponent(array &$element, $type, array $options, array $default_value) {
$element[$type] = [
'#type' => 'container',
'#attributes' => [
'class' => [
'container-inline',
],
],
];
$element[$type]['orientation'] = [
'#type' => 'select',
'#title' => '',
'#options' => $options,
'#multiple' => FALSE,
'#required' => (!empty($element['#required'])) ? $element['#required'] : FALSE,
'#default_value' => (!empty($default_value)) ? $default_value['orientation'] : '',
'#attributes' => [
'class' => [
'geofield-' . $type . '-orientation',
'container-inline',
],
'style' => [
'min-width: 6em',
],
],
];
$element[$type]['degrees'] = [
'#type' => 'number',
'#min' => 0,
'#step' => 1,
'#max' => 180,
'#title' => '',
'#required' => (!empty($element['#required'])) ? $element['#required'] : FALSE,
'#default_value' => (!empty($default_value)) ? $default_value['degrees'] : '',
'#suffix' => '°',
'#attributes' => [
'class' => [
'geofield-' . $type . '-degrees',
'container-inline',
],
],
];
$element[$type]['minutes'] = [
'#type' => 'number',
'#min' => 0,
'#max' => 59,
'#step' => 1,
'#title' => '',
'#required' => (!empty($element['#required'])) ? $element['#required'] : FALSE,
'#default_value' => (!empty($default_value)) ? $default_value['minutes'] : '',
'#suffix' => '\'',
'#attributes' => [
'class' => [
'geofield-' . $type . '-minutes',
'container-inline',
],
],
];
$element[$type]['seconds'] = [
'#type' => 'number',
'#min' => 0,
'#max' => 59,
'#step' => 1,
'#title' => '',
'#required' => (!empty($element['#required'])) ? $element['#required'] : FALSE,
'#default_value' => (!empty($default_value)) ? $default_value['seconds'] : '',
'#suffix' => '"',
'#attributes' => [
'class' => [
'geofield-' . $type . '-seconds',
'container-inline',
],
],
];
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Drupal\geofield\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElement;
/**
* Provides a base class for Geofield Form elements.
*/
abstract class GeofieldElementBase extends FormElement {
/**
* Components Getter.
*
* @return array
* Components Array.
*/
public static function getComponents() {
return [];
}
/**
* Generates a Geofield generic component based form element.
*
* @param array $element
* An associative array containing the properties and children of the
* element. Note that $element must be taken by reference here, so processed
* child elements are taken over into $form_state.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*/
public static function elementProcess(array &$element, FormStateInterface $form_state, array &$complete_form) {
$element['#tree'] = TRUE;
$element['#input'] = TRUE;
foreach (static::getComponents() as $name => $component) {
$element[$name] = [
'#type' => 'textfield',
'#title' => $component['title'],
'#required' => (!empty($element['#required'])) ? $element['#required'] : FALSE,
'#default_value' => (isset($element['#default_value'][$name])) ? $element['#default_value'][$name] : '',
'#attributes' => [
'class' => ['geofield-' . $name],
],
];
}
unset($element['#value']);
// Set this to false always to prevent notices.
$element['#required'] = FALSE;
return $element;
}
/**
* Validates a Geofield generic component based form element.
*
* @param array $element
* The element being processed.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*/
public static function elementValidate(array &$element, FormStateInterface $form_state, array &$complete_form) {
$error_label = $element['#error_label'] ?? $element['#title'];
foreach (static::getComponents() as $key => $component) {
if (!empty($element[$key]['#value']) && !is_numeric($element[$key]['#value'])) {
$form_state->setError($element[$key], t('@title: @component_title is not valid.', [
'@title' => $error_label,
'@component_title' => $component['title'],
]));
}
elseif (is_numeric($element[$key]['#value']) && abs($element[$key]['#value']) > $component['range']) {
$form_state->setError($element[$key], t('@title: @component_title is out of bounds (@bounds).', [
'@title' => $error_label,
'@component_title' => $component['title'],
'@bounds' => '+/- ' . $component['range'],
]));
}
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Drupal\geofield\Element;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a Geofield Lat Lon form element.
*
* @FormElement("geofield_latlon")
*/
class GeofieldLatLon extends GeofieldElementBase {
/**
* {@inheritdoc}
*/
public static function getComponents() {
return [
'lat' => [
'title' => t('Latitude'),
'range' => 90,
],
'lon' => [
'title' => t('Longitude'),
'range' => 180,
],
];
}
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#process' => [
[$class, 'latlonProcess'],
],
'#element_validate' => [
[$class, 'elementValidate'],
],
'#theme_wrappers' => ['fieldset'],
];
}
/**
* Generates the Geofield Lat Lon form element.
*
* @param array $element
* An associative array containing the properties and children of the
* element. Note that $element must be taken by reference here, so processed
* child elements are taken over into $form_state.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*/
public static function latlonProcess(array &$element, FormStateInterface $form_state, array &$complete_form) {
static::elementProcess($element, $form_state, $complete_form);
if (!empty($element['#geolocation']) && $element['#geolocation'] === TRUE) {
$element['#attached']['library'][] = 'geofield/geolocation';
$element['geocode'] = [
'#type' => 'button',
'#value' => t('Find my location'),
'#name' => 'geofield-html5-geocode-button',
];
$element['#attributes']['class'] = ['auto-geocode'];
}
return $element;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Drupal\geofield\Exception;
/**
* Defines 'haversine is unavailable' exception class.
*/
class HaversineUnavailableException extends \UnexpectedValueException {
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Drupal\geofield\Exception;
/**
* Defines 'invalid point' exception class.
*/
class InvalidPointException extends \InvalidArgumentException {
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Drupal\geofield\Exception;
/**
* Defines 'proximity value is unavailable' exception class.
*/
class ProximityUnavailableException extends \UnexpectedValueException {
}

View File

@@ -0,0 +1,160 @@
<?php
namespace Drupal\geofield\Feeds\Target;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\feeds\Exception\EmptyFeedException;
use Drupal\feeds\FieldTargetDefinition;
use Drupal\feeds\Plugin\Type\Target\FieldTargetBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a geofield field mapper.
*
* @FeedsTarget(
* id = "geofield_feeds_target",
* field_types = {"geofield"}
* )
*/
class Geofield extends FieldTargetBase implements ContainerFactoryPluginInterface {
/**
* The Settings object or array.
*
* @var mixed
*/
protected $settings;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a Geofield FeedsTarget object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, MessengerInterface $messenger) {
$this->targetDefinition = $configuration['target_definition'];
$this->settings = $this->targetDefinition->getFieldDefinition()->getSettings();
$this->messenger = $messenger;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('messenger')
);
}
/**
* {@inheritdoc}
*/
protected static function prepareTarget(FieldDefinitionInterface $field_definition) {
return FieldTargetDefinition::createFromFieldDefinition($field_definition)
->addProperty('lat')
->addProperty('lon')
->addProperty('value');
}
/**
* {@inheritdoc}
*/
protected function prepareValues(array $values) {
$results = [];
$coordinates = [];
foreach ($values as $delta => $columns) {
try {
$this->prepareValue($delta, $columns);
foreach ($columns as $column => $value) {
// Add Lat/Lon Coordinates.
if (in_array($column, ['lat', 'lon']) && isset($value)) {
foreach ($value as $item) {
$coordinates[$column][] = $item;
}
}
// Raw Geometry value (i.e. WKT or GeoJson).
if ($column == 'value') {
$results[]['value'] = $value;
}
}
}
catch (EmptyFeedException $e) {
$this->messenger->addError($e->getMessage());
return FALSE;
}
}
// Transform Lat/Lon Coordinates couples into WKT Points.
if (!empty($coordinates)) {
$count_of_coordinates = count($coordinates['lat']);
for ($i = 0; $i < $count_of_coordinates; $i++) {
// If either Latitude or Longitude is not null/zero then set a POINT.
if (!empty($coordinates['lat'][$i]) || !empty($coordinates['lon'][$i])) {
$results[]['value'] = "POINT (" . $coordinates['lon'][$i] . " " . $coordinates['lat'][$i] . ")";
}
}
}
return $results;
}
/**
* {@inheritdoc}
*/
protected function prepareValue($delta, array &$values) {
// Here is been preparing values for Lat/Lon coordinates.
foreach ($values as $column => $value) {
if (in_array($column, ['lat', 'lon']) && isset($value)) {
$separated_coordinates = explode(" ", $value);
$values[$column] = [];
foreach ($separated_coordinates as $coordinate) {
$values[$column][] = (float) $coordinate;
}
}
}
// Latitude and Longitude should be a pair, if not throw EmptyFeedException.
if (count($values['lat'] ?? []) != count($values['lon'] ?? [])) {
throw new EmptyFeedException('Latitude and Longitude should be a pair. Change your file and import again.');
}
}
/**
* {@inheritdoc}
*/
public function getSummary() {
$summary = parent::getSummary();
$summary[] = [
'#markup' => $this->t('<b>Instructions: </b>Use ONLY Centroid
Latitude & Centroid Longitude in case of Lat/Lon coupled values<br>OR ONLY Geometry in case of WKT or GeoJson format. Don\'t use both.'),
];
return $summary;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Drupal\geofield\GeoPHP;
/**
* Provides a GeoPHPInterface.
*/
interface GeoPHPInterface {
/**
* Retrieves the GeoPHP library current version.
*
* @return string
* The version value.
*/
public function version();
/**
* Loads a geometry object given some parameters.
*
* @param mixed|null $data
* The data to load.
* @param string $type
* The string type.
*
* @return \Geometry|null
* The geometry object
*/
public function load($data = NULL, $type = NULL);
/**
* Get the Adapter Map.
*
* @return mixed
* The Adapter Map.
*/
public function getAdapterMap();
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Drupal\geofield\GeoPHP;
/**
* Provides a geoPHP Wrapper class.
*/
class GeoPHPWrapper implements GeoPHPInterface {
/**
* {@inheritdoc}
*/
public function version() {
return \geoPHP::version();
}
/**
* {@inheritdoc}
*/
public function load($data = NULL, $type = NULL) {
try {
$geometry = call_user_func_array(['\geoPHP', 'load'], func_get_args());
return $geometry instanceof \Geometry ? $geometry : NULL;
}
catch (\Exception $e) {
return NULL;
}
}
/**
* {@inheritdoc}
*/
public function getAdapterMap() {
return call_user_func_array(['\geoPHP', 'getAdapterMap'], func_get_args());
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace Drupal\geofield\Plugin\Field\FieldFormatter;
use Drupal\Component\Utility\Html;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\geofield\GeoPHP\GeoPHPInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'geofield_default' formatter.
*
* @FieldFormatter(
* id = "geofield_default",
* label = @Translation("Raw Output"),
* field_types = {
* "geofield"
* }
* )
*/
class GeofieldDefaultFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
/**
* The geoPhpWrapper service.
*
* @var \Drupal\geofield\GeoPHP\GeoPHPInterface
*/
protected $geoPhpWrapper;
/**
* The Adapter Map Options.
*
* @var array
*/
protected $options;
/**
* GeofieldDefaultFormatter constructor.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param string $label
* The formatter label display setting.
* @param string $view_mode
* The view mode.
* @param array $third_party_settings
* Any third party settings.
* @param \Drupal\geofield\GeoPHP\GeoPHPInterface $geophp_wrapper
* The geoPhpWrapper.
*/
public function __construct(
$plugin_id,
$plugin_definition,
FieldDefinitionInterface $field_definition,
array $settings,
$label,
$view_mode,
array $third_party_settings,
GeoPHPInterface $geophp_wrapper,
) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
$this->geoPhpWrapper = $geophp_wrapper;
$this->options = $this->geoPhpWrapper->getAdapterMap();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['label'],
$configuration['view_mode'],
$configuration['third_party_settings'],
$container->get('geofield.geophp')
);
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'output_format' => 'wkt',
'output_escape' => TRUE,
];
}
/**
* Returns the output format, set or default one.
*
* @return string
* The output format string.
*/
protected function getOutputFormat() {
return in_array($this->getSetting('output_format'), array_keys($this->options)) ? $this->getSetting('output_format') : self::defaultSettings()['output_format'];
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
unset($this->options['google_geocode']);
$elements['output_format'] = [
'#title' => $this->t('Output Format'),
'#type' => 'select',
'#default_value' => $this->getOutputFormat(),
'#options' => $this->options,
'#required' => TRUE,
];
$elements['output_escape'] = [
'#title' => $this->t('Escape output (recommended)'),
'#description' => $this->t('The text is escaped by converting special characters to HTML entities.<br>In some circumstances (i.e. part of Json output) this might not be the wanted/preferred behavior.'),
'#type' => 'checkbox',
'#default_value' => $this->getSetting('output_escape'),
];
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = $this->t('Geospatial output format: @format', ['@format' => $this->getOutputFormat()]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
$geom = $this->geoPhpWrapper->load($item->value);
$output = $geom ? $geom->out($this->getOutputFormat()) : '';
if ($this->getSetting('output_escape')) {
$output = Html::escape($output);
}
$elements[$delta] = ['#markup' => $output];
}
return $elements;
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Drupal\geofield\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\geofield\DmsConverter;
/**
* Plugin implementation of the 'geofield_latlon' formatter.
*
* @FieldFormatter(
* id = "geofield_latlon",
* label = @Translation("Lat/Lon"),
* field_types = {
* "geofield"
* }
* )
*/
class LatLonFormatter extends GeofieldDefaultFormatter {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'output_format' => 'decimal',
];
}
/**
* Helper function to get the formatter settings options.
*
* @return array
* The formatter settings options.
*/
protected function formatOptions() {
return [
'decimal' => $this->t("Decimal Format (17.76972)"),
'dms' => $this->t("DMS Format (17° 46' 11'' N)"),
'dm' => $this->t("DM Format (17° 46.19214' N)"),
'wkt' => $this->t("WKT"),
];
}
/**
* Returns the output format, set or default one.
*
* @return string
* The output format string.
*/
protected function getOutputFormat() {
return in_array($this->getSetting('output_format'), array_keys($this->formatOptions())) ? $this->getSetting('output_format') : self::defaultSettings()['output_format'];
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
unset($elements['output_escape']);
$elements['output_format'] = [
'#title' => $this->t('Output Format'),
'#type' => 'select',
'#default_value' => $this->getOutputFormat(),
'#options' => $this->formatOptions(),
'#required' => TRUE,
];
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary[] = $this->t('Geospatial output format: @format', ['@format' => $this->formatOptions()[$this->getOutputFormat()]]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
$output = ['#markup' => ''];
$geom = $this->geoPhpWrapper->load($item->value);
if ($geom) {
// If the geometry is not a point, get the centroid.
if ($geom->getGeomType() != 'Point') {
$geom = $geom->centroid();
}
/** @var \Point $geom */
if ($this->getOutputFormat() == 'decimal') {
$output = [
'#theme' => 'geofield_latlon',
'#lat' => $geom->y(),
'#lon' => $geom->x(),
];
}
elseif ($this->getOutputFormat() == 'wkt') {
$output = [
'#markup' => "POINT ({$geom->x()} {$geom->y()})",
];
}
else {
$components = $this->getDmsComponents($geom);
$output = [
'#theme' => 'geofield_dms',
'#components' => $components,
];
}
}
$elements[$delta] = $output;
}
return $elements;
}
/**
* Generates the DMS expected components given a Point.
*
* @param \Point $point
* The point to represent as DMS.
*
* @return array
* The DMS LatLon components
*/
protected function getDmsComponents(\Point $point) {
$dms_point = DmsConverter::decimalToDms($point->x(), $point->y());
$components = [];
foreach (['lat', 'lon'] as $component) {
$item = $dms_point->get($component);
if ($this->getSetting('output_format') == 'dm') {
$item['minutes'] = number_format($item['minutes'] + ($item['seconds'] / 60), 5);
$item['seconds'] = NULL;
}
$components[$component] = $item;
}
return $components;
}
}

View File

@@ -0,0 +1,255 @@
<?php
namespace Drupal\geofield\Plugin\Field\FieldType;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'geofield' field type.
*
* @FieldType(
* id = "geofield",
* label = @Translation("Geofield"),
* description = @Translation("This field stores geospatial information."),
* default_widget = "geofield_latlon",
* default_formatter = "geofield_default"
* )
*/
class GeofieldItem extends FieldItemBase {
/**
* The Geofield Geometry.
*
* @var \Geometry|null
*/
private ?\Geometry $geometry;
/**
* {@inheritdoc}
*/
public static function defaultStorageSettings() {
return [
'backend' => 'geofield_backend_default',
] + parent::defaultStorageSettings();
}
/**
* {@inheritdoc}
*/
public static function defaultFieldSettings() {
return [] + parent::defaultFieldSettings();
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
/** @var \Drupal\geofield\Plugin\GeofieldBackendManager $backend_manager */
$backend_manager = \Drupal::service('plugin.manager.geofield_backend');
try {
/** @var \Drupal\geofield\Plugin\GeofieldBackendPluginInterface $backend_plugin */
if (!empty($field_definition->getSetting('backend')) && $backend_manager->getDefinition($field_definition->getSetting('backend')) != NULL) {
$backend_plugin = $backend_manager->createInstance($field_definition->getSetting('backend'));
}
}
catch (PluginException $e) {
\Drupal::service('logger.factory')->get('geofield')->error($e->getMessage());
}
return [
'columns' => [
'value' => isset($backend_plugin) ? $backend_plugin->schema() : [],
'geo_type' => [
'type' => 'varchar',
'default' => '',
'length' => 64,
],
'lat' => [
'type' => 'numeric',
'precision' => 18,
'scale' => 12,
'not null' => FALSE,
],
'lon' => [
'type' => 'numeric',
'precision' => 18,
'scale' => 12,
'not null' => FALSE,
],
'left' => [
'type' => 'numeric',
'precision' => 18,
'scale' => 12,
'not null' => FALSE,
],
'top' => [
'type' => 'numeric',
'precision' => 18,
'scale' => 12,
'not null' => FALSE,
],
'right' => [
'type' => 'numeric',
'precision' => 18,
'scale' => 12,
'not null' => FALSE,
],
'bottom' => [
'type' => 'numeric',
'precision' => 18,
'scale' => 12,
'not null' => FALSE,
],
'geohash' => [
'type' => 'varchar',
'length' => GEOFIELD_GEOHASH_LENGTH,
'not null' => FALSE,
],
],
'indexes' => [
'lat' => ['lat'],
'lon' => ['lon'],
'top' => ['top'],
'bottom' => ['bottom'],
'left' => ['left'],
'right' => ['right'],
'geohash' => ['geohash'],
'centroid' => ['lat', 'lon'],
'bbox' => ['top', 'bottom', 'left', 'right'],
],
];
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['value'] = DataDefinition::create('string')
->setLabel(t('Geometry'))
->addConstraint('GeoType', []);
$properties['geo_type'] = DataDefinition::create('string')
->setLabel(t('Geometry Type'));
$properties['lat'] = DataDefinition::create('float')
->setLabel(t('Centroid Latitude'));
$properties['lon'] = DataDefinition::create('float')
->setLabel(t('Centroid Longitude'));
$properties['left'] = DataDefinition::create('float')
->setLabel(t('Left Bounding'));
$properties['top'] = DataDefinition::create('float')
->setLabel(t('Top Bounding'));
$properties['right'] = DataDefinition::create('float')
->setLabel(t('Right Bounding'));
$properties['bottom'] = DataDefinition::create('float')
->setLabel(t('Bottom Bounding'));
$properties['geohash'] = DataDefinition::create('string')
->setLabel(t('Geohash'));
$properties['latlon'] = DataDefinition::create('string')
->setLabel(t('LatLong Pair'));
return $properties;
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$settings = $this->getSettings();
// Provides a field for the geofield storage backend plugin.
$backend_manager = \Drupal::service('plugin.manager.geofield_backend');
$backends = $backend_manager->getDefinitions();
$backend_options = [];
$backend_descriptions_list = '<ul>';
foreach ($backends as $id => $backend) {
$backend_options[$id] = $backend['admin_label'];
$backend_descriptions_list .= '<li>' . $backend['admin_label'] . ': ' . $backend['description'] . '</li>';
}
$element['backend'] = [
'#type' => 'select',
'#title' => $this->t('Storage backend'),
'#default_value' => $settings['backend'],
'#options' => $backend_options,
'#description' => [
'#markup' => $this->t('Select the Backend for storing Geofield data. The following are available: @backend_descriptions_list', [
'@backend_descriptions_list' => new FormattableMarkup($backend_descriptions_list, []),
]),
],
'#disabled' => $has_data,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
$value = $this->get('value')->getValue();
if (!empty($value)) {
/** @var \Drupal\geofield\GeoPHP\GeoPHPInterface $geo_php_wrapper */
// Note: Geofield FieldType doesn't support Dependency Injection yet
// (https://www.drupal.org/node/2053415).
$geo_php_wrapper = \Drupal::service('geofield.geophp');
$this->geometry = $geo_php_wrapper->load($value);
return $this->geometry instanceof \Geometry ? $this->geometry->isEmpty() : TRUE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function setValue($values, $notify = TRUE) {
parent::setValue($values);
$this->populateComputedValues();
}
/**
* Populates computed variables.
*/
protected function populateComputedValues() {
// Populate values only if $this->>value is not NULL.
// @see https://www.drupal.org/project/geofield/issues/3256644
// As passing null to parameter #2 ($data) of type string is deprecated in
// fwrite() of geoPHP::detectFormat()
// @see https://php.watch/versions/8.1/internal-func-non-nullable-null-deprecation
if (!$this->isEmpty()) {
/** @var \Point $centroid */
$centroid = $this->geometry->getCentroid();
$bounding = $this->geometry->getBBox();
$this->geo_type = $this->geometry->geometryType();
$this->lon = $centroid->getX();
$this->lat = $centroid->getY();
$this->left = $bounding['minx'];
$this->top = $bounding['maxy'];
$this->right = $bounding['maxx'];
$this->bottom = $bounding['miny'];
$this->geohash = substr($this->geometry->out('geohash'), 0, GEOFIELD_GEOHASH_LENGTH);
$this->latlon = $centroid->getY() . ',' . $centroid->getX();
}
}
/**
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
return [
'value' => \Drupal::service('geofield.wkt_generator')->WktGenerateGeometry(),
];
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace Drupal\geofield\Plugin\Field\FieldWidget;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\geofield\GeoPHP\GeoPHPInterface;
use Drupal\geofield\Plugin\GeofieldBackendManager;
use Drupal\geofield\WktGeneratorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Abstract class for Geofield widgets.
*/
abstract class GeofieldBaseWidget extends WidgetBase implements ContainerFactoryPluginInterface {
use LoggerChannelTrait;
/**
* The Geofield Backend setup for the specific Field definition.
*
* @var \Drupal\geofield\Plugin\GeofieldBackendPluginInterface|null
*/
protected $geofieldBackend = NULL;
/**
* The geoPhpWrapper service.
*
* @var \Drupal\geofield\GeoPHP\GeoPHPInterface
*/
protected $geoPhpWrapper;
/**
* The WKT format Generator service.
*
* @var \Drupal\geofield\WktGeneratorInterface
*/
protected $wktGenerator;
/**
* GeofieldBaseWidget constructor.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param array $third_party_settings
* Any third party settings.
* @param \Drupal\geofield\GeoPHP\GeoPHPInterface $geophp_wrapper
* The geoPhpWrapper.
* @param \Drupal\geofield\WktGeneratorInterface $wkt_generator
* The WKT format Generator service.
* @param \Drupal\geofield\Plugin\GeofieldBackendManager $geofield_backend_manager
* The geofieldBackendManager.
*/
public function __construct(
$plugin_id,
$plugin_definition,
FieldDefinitionInterface $field_definition,
array $settings,
array $third_party_settings,
GeoPHPInterface $geophp_wrapper,
WktGeneratorInterface $wkt_generator,
GeofieldBackendManager $geofield_backend_manager = NULL,
) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
try {
if ($geofield_backend_manager instanceof GeofieldBackendManager) {
$this->geofieldBackend = $geofield_backend_manager->createInstance($field_definition->getSetting("backend"));
}
}
catch (PluginException $e) {
$this->getLogger('geofield')->error($e->getMessage());
}
$this->geoPhpWrapper = $geophp_wrapper;
$this->wktGenerator = $wkt_generator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['third_party_settings'],
$container->get('geofield.geophp'),
$container->get('geofield.wkt_generator'),
$container->get('plugin.manager.geofield_backend')
);
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
// Attach Geofield Libraries.
$element['#attached']['library'] = [
'geofield/geofield_general',
];
return ['value' => $element];
}
/**
* Return the specific Geofield Backend Value.
*
* Falls back into WKT format, in case Geofield Backend undefined.
*
* @param mixed|null $value
* The data to load.
*
* @return mixed|null
* The specific backend format value.
*/
protected function geofieldBackendValue($value) {
$output = NULL;
/** @var \Geometry|null $geom */
if ($this->geofieldBackend && $geom = $this->geoPhpWrapper->load($value)) {
$output = $this->geofieldBackend->save($geom);
}
elseif ($geom = $this->geoPhpWrapper->load($value)) {
$output = $geom->out('wkt');
}
return $output;
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Drupal\geofield\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'geofield_bounds' widget.
*
* @FieldWidget(
* id = "geofield_bounds",
* label = @Translation("Bounding Box"),
* field_types = {
* "geofield"
* }
* )
*/
class GeofieldBoundsWidget extends GeofieldBaseWidget {
/**
* Bounds widget components.
*
* @var array
*/
public $components = ['top', 'right', 'bottom', 'left'];
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element = parent::formElement($items, $delta, $element, $form, $form_state);
$bounds_value = [];
foreach ($this->components as $component) {
$bounds_value[$component] = isset($items[$delta]->{$component}) ? floatval($items[$delta]->{$component}) : '';
}
$element['value'] += [
'#type' => 'geofield_bounds',
'#default_value' => $bounds_value,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
foreach ($values as $delta => $value) {
foreach ($this->components as $component) {
if (empty($value['value'][$component]) || !is_numeric($value['value'][$component])) {
$values[$delta]['value'] = '';
continue 2;
}
}
$components = $value['value'];
$bounds = [
[$components['right'], $components['top']],
[$components['right'], $components['bottom']],
[$components['left'], $components['bottom']],
[$components['left'], $components['top']],
[$components['right'], $components['top']],
];
$values[$delta]['value'] = $this->wktGenerator->wktBuildPolygon($bounds);
}
return $values;
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Drupal\geofield\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Widget implementation of the 'geofield_default' widget.
*
* @FieldWidget(
* id = "geofield_default",
* label = @Translation("Geofield (WKT)"),
* field_types = {
* "geofield"
* }
* )
*/
class GeofieldDefaultWidget extends GeofieldBaseWidget {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'geometry_validation' => FALSE,
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
$elements['geometry_validation'] = [
'#type' => 'checkbox',
'#title' => 'Enable Geometry Validation',
'#default_value' => $this->getSetting('geometry_validation'),
'#description' => $this->t('Enable input Geometry validation, in WKT or Geojson format. If not checked invalid Geometries will be set as NULL.'),
];
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
return [
$this->t('Geometry Validation: @state', ['@state' => $this->getSetting('geometry_validation') ? $this->t('enabled') : $this->t('disabled')]),
];
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element += [
'#type' => 'textarea',
'#default_value' => $items[$delta]->value ?: NULL,
];
if ($this->getSetting('geometry_validation')) {
// Append notice to the field description in the widget:
$element['#description'] = $element['#description'] . '<br />' . $this->t('Geometry Validation enabled (valid WKT or Geojson format & values required)');
$element['#element_validate'] = [
[get_class($this), 'validateGeofieldGeometryText'],
];
}
else {
// Append notice to the field description in the widget:
$element['#description'] = $element['#description'] . '<br />' . $this->t('Geometry Validation disabled (invalid WKT or Geojson format & values will be set as NULL)');
}
return ['value' => $element];
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
foreach ($values as $delta => $value) {
$values[$delta]['value'] = $this->geofieldBackendValue($value['value']);
}
return $values;
}
/**
* {@inheritdoc}
*/
public static function validateGeofieldGeometryText(array $element, FormStateInterface $form_state) {
if (!empty($element['#value']) && is_null(\Drupal::service('geofield.geophp')->load($element['#value']))) {
$form_state->setError($element, t('The @value is not a valid geospatial content.', [
'@value' => $element['#value'],
]));
}
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace Drupal\geofield\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\geofield\DmsConverter;
use Drupal\geofield\DmsPoint;
/**
* Plugin implementation of the 'geofield_dms' widget.
*
* @FieldWidget(
* id = "geofield_dms",
* label = @Translation("Degrees Minutes Seconds"),
* field_types = {
* "geofield"
* }
* )
*/
class GeofieldDmsWidget extends GeofieldBaseWidget {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element = parent::formElement($items, $delta, $element, $form, $form_state);
/** @var \Drupal\geofield\Plugin\Field\FieldType\GeofieldItem $geofield_item */
$geofield_item = $items->getValue()[$delta];
if (empty($geofield_item) || $geofield_item['geo_type'] == 'Point') {
$latlon_value = [];
foreach (['lat', 'lon'] as $component) {
$latlon_value[$component] = isset($items[$delta]->{$component}) ? floatval($items[$delta]->{$component}) : '';
}
$element['value'] += [
'#type' => 'geofield_dms',
'#default_value' => $latlon_value,
];
}
else {
$widget_label = $this->getPluginDefinition()['label']->render();
$element['value'] += [
'#prefix' => '<div class="geofield-warning">' . $this->t('The "@widget_label" widget cannot be applied because it doesn\'t support Geometries (Polylines, Polygons, etc.).', [
'@widget_label' => $widget_label,
]) . '</div>',
'#type' => 'textarea',
'#default_value' => $items[$delta]->value ?: NULL,
];
}
return $element;
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
foreach ($values as $delta => $value) {
// Generate a valid Geofield only if the DMS coordinates are valid.
if (
is_numeric($value['value']['lon']['degrees']) &&
is_numeric($value['value']['lon']['minutes']) &&
is_numeric($value['value']['lon']['seconds']) &&
is_numeric($value['value']['lat']['degrees']) &&
is_numeric($value['value']['lat']['minutes']) &&
is_numeric($value['value']['lat']['seconds'])
) {
$components = DmsConverter::dmsToDecimal(new DmsPoint($value['value']['lon'], $value['value']['lat']));
$values[$delta]['value'] = $this->geofieldBackendValue($this->wktGenerator->wktGeneratePoint($components));
}
// Otherwise delete the entry.
else {
$values[$delta]['value'] = NULL;
}
}
return $values;
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace Drupal\geofield\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'geofield_latlon' widget.
*
* @FieldWidget(
* id = "geofield_latlon",
* label = @Translation("Latitude/Longitude"),
* field_types = {
* "geofield"
* }
* )
*/
class GeofieldLatLonWidget extends GeofieldBaseWidget {
/**
* Lat Lon widget components.
*
* @var array
*/
public $components = ['lon', 'lat'];
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'html5_geolocation' => FALSE,
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
$elements['html5_geolocation'] = [
'#type' => 'checkbox',
'#title' => 'Use HTML5 Geolocation to set default values',
'#default_value' => $this->getSetting('html5_geolocation'),
];
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
return [
$this->t('Use HTML5 Geolocation of user: @state', ['@state' => $this->getSetting('html5_geolocation') ? $this->t('enabled') : $this->t('disabled')]),
];
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element = parent::formElement($items, $delta, $element, $form, $form_state);
/** @var \Drupal\geofield\Plugin\Field\FieldType\GeofieldItem $geofield_item */
$geofield_item = $items->getValue()[$delta];
if (empty($geofield_item) || $geofield_item['geo_type'] == 'Point') {
$latlon_value = [];
foreach ($this->components as $component) {
$latlon_value[$component] = isset($items[$delta]->{$component}) ? floatval($items[$delta]->{$component}) : '';
}
$element['value'] += [
'#type' => 'geofield_latlon',
'#default_value' => $latlon_value,
'#geolocation' => $this->getSetting('html5_geolocation'),
'#error_label' => !empty($element['#title']) ? $element['#title'] : $this->fieldDefinition->getLabel(),
];
}
else {
$widget_label = $this->getPluginDefinition()['label']->render();
$element['value'] += [
'#prefix' => '<div class="geofield-warning">' . $this->t('The "@widget_label" widget cannot be applied because it doesn\'t support Geometries (Polylines, Polygons, etc.).', [
'@widget_label' => $widget_label,
]) . '</div>',
'#type' => 'textarea',
'#default_value' => $items[$delta]->value ?: NULL,
];
}
return $element;
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
foreach ($values as $delta => $value) {
foreach ($this->components as $component) {
if (!isset($value['value'][$component]) || !is_numeric($value['value'][$component])) {
$values[$delta]['value'] = '';
continue 2;
}
}
$components = $value['value'];
$values[$delta]['value'] = $this->geofieldBackendValue($this->wktGenerator->wktBuildPoint([
trim($components['lon']),
trim($components['lat']),
]));
}
return $values;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Drupal\geofield\Plugin\GeofieldBackend;
use Drupal\geofield\Plugin\GeofieldBackendBase;
/**
* Default backend for Geofield.
*
* Definition of a default Geofield Backend for storing values in WKT Format.
*
* @GeofieldBackend(
* id = "geofield_backend_default",
* admin_label = @Translation("Default (WKT)"),
* description = @Translation("Default Geofield Backend for storing values in WKT Format")
* )
*/
class GeofieldBackendDefault extends GeofieldBackendBase {
/**
* {@inheritdoc}
*/
public function schema() {
return [
'type' => 'blob',
'size' => 'big',
'not null' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public function save($geometry) {
$output = NULL;
if ($geom = $this->geoPhpWrapper->load($geometry)) {
$output = $geom->out('wkt');
}
return $output;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Drupal\geofield\Plugin\GeofieldBackend;
use Drupal\geofield\Plugin\GeofieldBackendBase;
/**
* PostgreSQL/PostGIS Backend for Geofield.
*
* @GeofieldBackend(
* id = "geofield_backend_postgis",
* admin_label = @Translation("PostGIS Geometry"),
* description = @Translation("Geofield Backend storing values in EWKB Format, suitable for PostgreSQL/PostGIS (needs PostGis enabled)")
* )
*/
class GeofieldBackendPostgis extends GeofieldBackendBase {
/**
* {@inheritdoc}
*/
public function schema() {
return [
'type' => 'blob',
'not null' => FALSE,
'pgsql_type' => 'geometry',
];
}
/**
* {@inheritdoc}
*/
public function save($geometry) {
$geom = $this->geoPhpWrapper->load($geometry);
$unpacked = unpack('H*', $geom->out('ewkb'));
return $unpacked[1];
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Drupal\geofield\Plugin;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\geofield\GeoPHP\GeoPHPInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a base class for geofield backends.
*
* A complete sample plugin definition should be defined as in this example:
*
* @code
* @GeofieldBackend(
* id = "geofield_backend_default",
* admin_label = @Translation("Default Backend")
* )
* @endcode
*
* @see \Drupal\geofield\Annotation\GeofieldBackend
* @see \Drupal\geofield\Plugin\GeofieldBackendPluginInterface
* @see \Drupal\geofield\Plugin\GeofieldBackendManager
* @see plugin_api
*/
abstract class GeofieldBackendBase extends PluginBase implements GeofieldBackendPluginInterface, ContainerFactoryPluginInterface {
/**
* The geoPhpWrapper service.
*
* @var \Drupal\geofield\GeoPHP\GeoPHPInterface
*/
protected $geoPhpWrapper;
/**
* Constructs the GeofieldBackendDefault.
*
* @param array $configuration
* The configuration.
* @param string $plugin_id
* The plugin ID for the migration process to do.
* @param mixed $plugin_definition
* The configuration for the plugin.
* @param \Drupal\geofield\GeoPHP\GeoPHPInterface $geophp_wrapper
* The geoPhpWrapper.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
GeoPHPInterface $geophp_wrapper,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->geoPhpWrapper = $geophp_wrapper;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('geofield.geophp')
);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Drupal\geofield\Plugin;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
/**
* Defines the plugin manager Geofield backends.
*/
class GeofieldBackendManager extends DefaultPluginManager {
/**
* Constructs a new \Drupal\geofield\Plugin\GeofieldBackendManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/GeofieldBackend', $namespaces, $module_handler, 'Drupal\geofield\Plugin\GeofieldBackendPluginInterface', 'Drupal\geofield\Annotation\GeofieldBackend');
$this->alterInfo('geofield');
$this->setCacheBackend($cache_backend, 'geofield_plugins');
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Drupal\geofield\Plugin;
use Drupal\Component\Plugin\PluginInspectionInterface;
/**
* Defines an interface for Geofield backends.
*
* Modules implementing this interface may want to extend GeofieldBackendBase
* class, which provides default implementations of each method.
*
* @see \Drupal\geofield\Annotation\GeofieldBackend
* @see \Drupal\geofield\Plugin\GeofieldBackendBase
* @see \Drupal\geofield\Plugin\GeofieldBackendManager
* @see plugin_api
*/
interface GeofieldBackendPluginInterface extends PluginInspectionInterface {
/**
* Provides the specific database schema for the specific backend.
*
* @return array
* The schema value array.
*/
public function schema();
/**
* Saves the Geo value into the Specific Backend Format.
*
* @param mixed|null $geometry
* The Geometry to save.
*
* @return mixed|null
* The specific backend format value.
*/
public function save($geometry);
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Drupal\geofield\Plugin\GeofieldProximitySource;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines 'Geofield Client Location Origin' plugin.
*
* @package Drupal\geofield\Plugin
*
* @GeofieldProximitySource(
* id = "geofield_client_location_origin",
* label = @Translation("Client Location Origin"),
* description = @Translation("Gets the Client Location through the browser HTML5 Geolocation API."),
* context = {
* "filter",
* },
* exposedOnly = true
* )
*/
class ClientLocationOriginFilter extends ManualOriginDefault {
/**
* {@inheritdoc}
*/
public function buildOptionsForm(array &$form, FormStateInterface $form_state, array $options_parents, $is_exposed = FALSE) {
// For the Client Location Origin Filter. Lat and Lon are being set only on
// the Client Front end, thus we set them as initially null.
$lat = NULL;
$lon = NULL;
if ($is_exposed) {
$form['#attributes']['class'][] = 'proximity-origin-client';
}
$form["origin"] = [
'#title' => $this->t('Client Coordinates'),
'#type' => 'geofield_latlon',
'#description' => $this->t('Value in decimal degrees. Use dot (.) as decimal separator.'),
'#default_value' => [
'lat' => $lat,
'lon' => $lon,
],
'#attributes' => [
'class' => ['proximity-origin-input visually-hidden'],
],
];
// If it is a proximity filter context and IS NOT exposed, render origin
// summary option.
if ($this->viewHandler->configuration['id'] == 'geofield_proximity_filter' && !$is_exposed) {
$form['origin_summary_flag'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show the Client Origin coordinates as summary in the Exposed Form'),
'#default_value' => $this->configuration['origin_summary_flag'] ?? TRUE,
];
}
// If it IS exposed load the geolocation library.
if ($is_exposed) {
$form['origin']['#attached']['library'][] = 'geofield/geolocation';
// And eventually Render the Origin Summary.
if (isset($this->configuration['origin_summary_flag']) && $this->configuration['origin_summary_flag']) {
$form['origin_summary'] = [
"#type" => 'html_tag',
"#tag" => 'div',
'#value' => $this->t('from Latitude: @lat and Longitude: @lon.', [
'@lat' => new FormattableMarkup('<span class="geofield-lat-summary">@lat</span>', [
'@lat' => $this->t('undefined'),
]),
'@lon' => new FormattableMarkup('<span class="geofield-lon-summary">@lon</span>', [
'@lon' => $this->t('undefined'),
]),
]),
'#attributes' => [
'class' => ['proximity-origin-summary'],
],
];
$form['origin_summary']['#attached']['library'][] = 'geofield/proximity_origin_summary_update';
}
}
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Drupal\geofield\Plugin\GeofieldProximitySource;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\geofield\Plugin\GeofieldProximitySourceBase;
use Drupal\geofield\Plugin\GeofieldProximitySourceManager;
use Drupal\geofield\Plugin\views\argument\GeofieldProximityArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines 'Geofield Context Filter' plugin.
*
* @package Drupal\geofield\Plugin
*
* @GeofieldProximitySource(
* id = "geofield_context_filter",
* label = @Translation("Context Filter (By context filter)"),
* description = @Translation("Allow the contextual input of Origin as couple of Latitude and Longitude in decimal degrees."),
* exposedDescription = @Translation("Contextual input of Origin as couple of Latitude and Longitude in decimal degrees."),
* context = {
* "sort",
* "field",
* },
* )
*/
class ContextProximityFilter extends GeofieldProximitySourceBase implements ContainerFactoryPluginInterface {
/**
* The geofield proximity manager.
*
* @var \Drupal\geofield\Plugin\GeofieldProximitySourceManager
*/
protected $proximitySourceManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.geofield_proximity_source')
);
}
/**
* Constructs a GeocodeOrigin object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\geofield\Plugin\GeofieldProximitySourceManager $proximitySourceManager
* The Geofield Proximity Source manager service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, GeofieldProximitySourceManager $proximitySourceManager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->proximitySourceManager = $proximitySourceManager;
}
/**
* {@inheritdoc}
*/
public function getOrigin() {
$origin = [];
if (isset($this->viewHandler)) {
foreach ($this->viewHandler->view->argument as $argument) {
if ($argument instanceof GeofieldProximityArgument && $argument_values = $argument->getParsedReferenceLocation()) {
$origin = [
'lat' => $argument_values['lat'],
'lon' => $argument_values['lon'],
];
}
}
}
return $origin;
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace Drupal\geofield\Plugin\GeofieldProximitySource;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Form\FormStateInterface;
use Drupal\geofield\Plugin\GeofieldProximitySourceBase;
/**
* Defines 'Geofield Manual Origin' plugin.
*
* @package Drupal\geofield\Plugin
*
* @GeofieldProximitySource(
* id = "geofield_manual_origin",
* label = @Translation("Manual Origin (Default)"),
* description = @Translation("Allow the Manual input of Origin as couple of Latitude and Longitude in decimal degrees."),
* exposedDescription = @Translation("Manual input of Distance and Origin (as couple of Latitude and Longitude in decimal degrees.)"),
* context = {},
* )
*/
class ManualOriginDefault extends GeofieldProximitySourceBase {
/**
* Constructs a ManualOriginDefault object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->origin['lat'] = isset($configuration['origin']) && is_numeric($configuration['origin']['lat']) ? $configuration['origin']['lat'] : '';
$this->origin['lon'] = isset($configuration['origin']) && is_numeric($configuration['origin']['lon']) ? $configuration['origin']['lon'] : '';
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(array &$form, FormStateInterface $form_state, array $options_parents, $is_exposed = FALSE) {
$user_input = $form_state->getUserInput();
$origin = $this->origin;
if ($is_exposed && isset($user_input["field_geofield_proximity"]["source_configuration"]["origin"])) {
$origin = $user_input["field_geofield_proximity"]["source_configuration"]["origin"];
}
$lat = $origin['lat'];
$lon = $origin['lon'];
$form['#attributes'] = [
'class' => ['proximity-origin'],
];
$form["origin"] = [
'#title' => $this->t('Origin Coordinates'),
'#type' => 'geofield_latlon',
'#description' => $this->t('Value in decimal degrees. Use dot (.) as decimal separator.'),
'#default_value' => [
'lat' => $lat,
'lon' => $lon,
],
'#attributes' => [
'class' => ['proximity-origin-input'],
],
];
// If it is a proximity filter context and IS NOT exposed, render origin
// hidden and origin_summary options.
if ($this->viewHandler->configuration['id'] == 'geofield_proximity_filter' && !$is_exposed) {
$form['origin_hidden_flag'] = [
'#type' => 'checkbox',
'#title' => $this->t('Hide the Origin Input elements from the Exposed Form'),
'#default_value' => $this->configuration['origin_hidden_flag'] ?? FALSE,
'#states' => [
'visible' => [
':input[name="options[expose_button][checkbox][checkbox]"]' => ['checked' => TRUE],
],
],
];
$form['origin_summary_flag'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show (anyway) the Origin coordinates as summary in the Exposed Form'),
'#default_value' => $this->configuration['origin_summary_flag'] ?? TRUE,
'#states' => [
'visible' => [
':input[name="options[source_configuration][origin_hidden_flag]"]' => ['checked' => TRUE],
],
],
];
}
// If it IS exposed, eventually Hide the Origin components..
if ($is_exposed && (isset($this->configuration['origin_hidden_flag']) && $this->configuration['origin_hidden_flag'])) {
$form["origin"]['#attributes']['class'][] = 'visually-hidden';
// Eventually Render the Origin Summary.
if (isset($this->configuration['origin_summary_flag']) && $this->configuration['origin_summary_flag']) {
$form['origin_summary'] = [
"#type" => 'html_tag',
"#tag" => 'div',
'#value' => $this->t('from Latitude: @lat and Longitude: @lon.', [
'@lat' => new FormattableMarkup('<span class="geofield-lat geofield-lat-summary"> @lat</span>', [
'@lat' => !empty($lat) ? $lat : $this->t('undefined'),
]),
'@lon' => new FormattableMarkup('<span class="geofield-lon geofield-lon-summary"> @lon</span>', [
'@lon' => !empty($lon) ? $lon : $this->t('undefined'),
]),
]),
'#attributes' => [
'class' => ['proximity-origin-summary'],
],
];
$form['origin_summary']['#attached']['library'][] = 'geofield/proximity_origin_summary_update';
}
}
}
}

View File

@@ -0,0 +1,187 @@
<?php
namespace Drupal\geofield\Plugin\GeofieldProximitySource;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\geofield\Plugin\GeofieldProximitySourceBase;
use Drupal\geofield\Plugin\GeofieldProximitySourceManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines 'Geofield Custom Origin' plugin.
*
* @package Drupal\geofield\Plugin
*
* @GeofieldProximitySource(
* id = "geofield_origin_from_proximity_filter",
* label = @Translation("Origin from Proximity Filter"),
* description = @Translation("A sort and field plugin that points the Origin from an existing Geofield Proximity Filter."),
* exposedDescription = @Translation("The origin is fixed from an existing Geofield Proximity Filter."),
* context = {
* "sort",
* "field",
* }
* )
*/
class OriginFromProximityFilter extends GeofieldProximitySourceBase implements ContainerFactoryPluginInterface {
use LoggerChannelTrait;
/**
* The geofield proximity manager.
*
* @var \Drupal\geofield\Plugin\GeofieldProximitySourceManager
*/
protected $proximitySourceManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.geofield_proximity_source')
);
}
/**
* Constructs a GeocodeOrigin object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\geofield\Plugin\GeofieldProximitySourceManager $proximitySourceManager
* The Geofield Proximity Source manager service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, GeofieldProximitySourceManager $proximitySourceManager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->proximitySourceManager = $proximitySourceManager;
}
/**
* Returns the list of available proximity filters.
*
* @return array
* The list of available proximity filters
*/
protected function getAvailableProximityFilters() {
$proximity_filters = [];
/** @var \Drupal\views\Plugin\views\filter\FilterPluginBase $filter */
foreach ($this->viewHandler->displayHandler->getHandlers('filter') as $delta => $filter) {
if ($filter->pluginId === 'geofield_proximity_filter') {
$proximity_filters[$delta] = $filter->adminLabel();
}
}
return $proximity_filters;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(array &$form, FormStateInterface $form_state, array $options_parents, $is_exposed = FALSE) {
$user_input = $form_state->getUserInput();
$proximity_filters_sources = $this->getAvailableProximityFilters();
$user_input_proximity_filter = $user_input['options']['source_configuration']['source_proximity_filter'] ?? current(array_keys($proximity_filters_sources));
$source_proximity_filter = $this->configuration['source_proximity_filter'] ?? $user_input_proximity_filter;
if (!empty($proximity_filters_sources)) {
$form['source_proximity_filter'] = [
'#type' => 'select',
'#title' => $this->t('Source Proximity Filter'),
'#description' => $this->t('Select the Geofield Proximity filter to use as the starting point for calculating proximity.'),
'#options' => $this->getAvailableProximityFilters(),
'#default_value' => $source_proximity_filter,
'#ajax' => [
'callback' => [static::class, 'sourceProximityFilterUpdate'],
'effect' => 'fade',
],
];
}
else {
$form['source_proximity_filter_warning'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => $this->t('No Geofield Proximity Filter found. At least one should be set for this Proximity Field be able to work.'),
"#attributes" => [
'class' => ['geofield-warning', 'red'],
],
];
$form_state->setError($form['source_proximity_filter_warning'], $this->t('This Proximity Field cannot work. Dismiss this and add & setup a Geofield Proximity Filter before.'));
}
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(array &$form, FormStateInterface $form_state, array $options_parents) {
$values = $form_state->getValues();
if (!isset($values['options']['source_configuration']['source_proximity_filter'])) {
$form_state->setError($form['source_proximity_filter_warning'], $this->t('This Proximity Field cannot work. Dismiss this and add and setup a Proximity Filter before.'));
}
}
/**
* Ajax callback triggered on Proximity Filter Selection.
*
* @param array $form
* The build form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* Ajax response with updated form element.
*/
public static function sourceProximityFilterUpdate(array $form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new ReplaceCommand(
'#proximity-source-configuration',
$form['options']['source_configuration']
));
return $response;
}
/**
* {@inheritdoc}
*/
public function getOrigin() {
$origin = [];
if (isset($this->viewHandler)
&& isset($this->viewHandler->view->filter[$this->viewHandler->options['source_configuration']['source_proximity_filter']])
&& is_a($this->viewHandler->view->filter[$this->viewHandler->options['source_configuration']['source_proximity_filter']], '\Drupal\geofield\Plugin\views\filter\GeofieldProximityFilter')
&& $source_proximity_filter = $this->viewHandler->options['source_configuration']['source_proximity_filter']
) {
/** @var \Drupal\geofield\Plugin\views\filter\GeofieldProximityFilter $geofield_proximity_filter */
$geofield_proximity_filter = $this->viewHandler->view->filter[$source_proximity_filter];
$source_plugin_id = $geofield_proximity_filter->options['source'];
$source_plugin_configuration = $geofield_proximity_filter->options['source_configuration'];
try {
/** @var \Drupal\geofield\Plugin\GeofieldProximitySourceInterface $source_plugin */
$source_plugin = $this->proximitySourceManager->createInstance($source_plugin_id, $source_plugin_configuration);
$source_plugin->setViewHandler($geofield_proximity_filter);
$origin = $source_plugin->getOrigin();
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
}
}
return $origin;
}
}

View File

@@ -0,0 +1,185 @@
<?php
namespace Drupal\geofield\Plugin;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\geofield\Exception\HaversineUnavailableException;
use Drupal\geofield\Exception\InvalidPointException;
use Drupal\geofield\Exception\ProximityUnavailableException;
use Drupal\views\Plugin\views\HandlerBase;
/**
* Base class for Geofield Proximity Source plugins.
*/
abstract class GeofieldProximitySourceBase extends PluginBase implements GeofieldProximitySourceInterface {
use StringTranslationTrait;
/**
* The name of the constant defining the measurement unit.
*
* @var string
*/
protected $units;
/**
* The view handler which uses this proximity plugin.
*
* @var \Drupal\views\Plugin\views\HandlerBase
*/
protected $viewHandler;
/**
* The origin point to measure proximity from.
*
* @var array
*/
protected $origin;
/**
* {@inheritdoc}
*/
public function isValidLocation($lat, $lon) {
return is_numeric($lat) && is_numeric($lon);
}
/**
* {@inheritdoc}
*/
public function isEmptyLocation($lat, $lon) {
return (empty($lat) && empty($lon));
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(array &$form, FormStateInterface $form_state, array $options_parents, $is_exposed = FALSE) {
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(array &$form, FormStateInterface $form_state, array $options_parents) {
}
/**
* {@inheritdoc}
*/
public function getOrigin() {
return $this->origin;
}
/**
* {@inheritdoc}
*/
public function setOrigin(array $origin) {
return $this->origin = $origin;
}
/**
* {@inheritdoc}
*/
public function setUnits($units) {
// If the given value is not a valid option, throw an error.
if (!in_array($units, $this->getUnitsOptions())) {
$message = $this->t('Invalid units supplied.');
\Drupal::logger('geofield')->error($message);
return FALSE;
}
// Otherwise set units to the given value.
else {
$this->units = $units;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getUnits() {
return $this->units;
}
/**
* Get the list of valid options for units.
*
* @return array
* The list of available unit types.
*/
public function getUnitsOptions() {
return array_keys(geofield_radius_options());
}
/**
* {@inheritdoc}
*/
public function setViewHandler(HandlerBase $view_handler) {
$this->viewHandler = $view_handler;
}
/**
* {@inheritdoc}
*/
public function getProximity($lat, $lon) {
if (!$this->isValidLocation($lat, $lon)) {
throw new InvalidPointException(sprintf('%s reports Invalid Point coordinates', get_class($this)));
}
// Fetch the value of the units that have been set for this class. The
// constants are defined in the module file.
$radius = constant($this->units);
$origin = $this->getOrigin();
if (!isset($origin['lat']) || !isset($origin['lon']) || $this->isEmptyLocation($origin['lat'], $origin['lon'])) {
return NULL;
}
// Convert degrees to radians.
$origin_latitude = deg2rad($origin['lat']);
$origin_longitude = deg2rad($origin['lon']);
$destination_latitude = deg2rad($lat);
$destination_longitude = deg2rad($lon);
// Calculate proximity.
$proximity = $radius * acos(
cos($origin_latitude)
* cos($destination_latitude)
* cos($destination_longitude - $origin_longitude)
+ sin($origin_latitude)
* sin($destination_latitude)
);
if (!is_numeric($proximity)) {
throw new ProximityUnavailableException(sprintf('%s not able to calculate valid Proximity value', get_class($this)));
}
return $proximity;
}
/**
* {@inheritdoc}
*/
public function getHaversineOptions() {
$origin = $this->getOrigin();
if (!$origin || !isset($origin['lat']) || !isset($origin['lon'])) {
throw new HaversineUnavailableException('Not able to calculate Haversine Options due to invalid Proximity Origin definition.');
}
if ($this->isEmptyLocation($origin['lat'], $origin['lon']) || !$this->isValidLocation($origin['lat'], $origin['lon'])) {
return NULL;
}
return [
'origin_latitude' => $origin['lat'],
'origin_longitude' => $origin['lon'],
'earth_radius' => constant($this->units),
];
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace Drupal\geofield\Plugin;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\HandlerBase;
/**
* Defines an interface for Geofield Proximity Source plugins.
*/
interface GeofieldProximitySourceInterface extends PluginInspectionInterface {
/**
* Check for a valid couple of latitude and longitude.
*
* @param float $lat
* The latitude value.
* @param float $lon
* The longitude value.
*
* @return bool
* The flag indicates whether location is valid.
*
* @todo Add more tests, particularly around max/min values.
*/
public function isValidLocation($lat, $lon);
/**
* Check if Location is empty.
*
* @param float $lat
* The latitude value.
* @param float $lon
* The longitude value.
*
* @return bool
* The bool result.
*/
public function isEmptyLocation($lat, $lon);
/**
* Builds the specific form elements for the geofield proximity plugin.
*
* @param array $form
* The form element to build.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param array $options_parents
* The values parents.
* @param bool $is_exposed
* The check/differentiate if it is part of an exposed form.
*/
public function buildOptionsForm(array &$form, FormStateInterface $form_state, array $options_parents, $is_exposed = FALSE);
/**
* Validates the options form for the geofield proximity plugin.
*
* @param array $form
* The form element to build.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param array $options_parents
* The values parents.
*/
public function validateOptionsForm(array &$form, FormStateInterface $form_state, array $options_parents);
/**
* Set the units to perform the calculation in.
*
* @param string $units
* The name of the units constant to be used or string representation of it.
*/
public function setUnits($units);
/**
* Get the current units.
*
* @return string
* The name of the units constant to be used or string representation of it.
*/
public function getUnits();
/**
* Sets view handler which uses this proximity plugin.
*
* @param \Drupal\views\Plugin\views\HandlerBase $view_handler
* The view handler which uses this proximity plugin.
*/
public function setViewHandler(HandlerBase $view_handler);
/**
* Get the calculated proximity.
*
* @param float $lat
* The current point latitude.
* @param float $lon
* The current point longitude.
*
* @return float
* The calculated proximity.
*
* @throws \Drupal\geofield\Exception\InvalidPointException;
* If the proximity cannot be created, due to incorrect point coordinates
* definition.
*
* @throws \Drupal\geofield\Exception\ProximityUnavailableException;
* If any other case the proximity value cannot be created correctly.
*/
public function getProximity($lat, $lon);
/**
* Gets the haversine options.
*
* @return array
* The haversine options.
*
* @throws \Drupal\geofield\Exception\HaversineUnavailableException;
* If the haversine is unavailable, due to incorrect setup definitions.
*/
public function getHaversineOptions();
/**
* Gets the proximity distance origin.
*
* @return array
* The proximity distance origin.
*/
public function getOrigin();
/**
* Sets the proximity distance origin.
*
* @param array $origin
* The proximity distance origin.
*/
public function setOrigin(array $origin);
}

View File

@@ -0,0 +1,127 @@
<?php
namespace Drupal\geofield\Plugin;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Provides the Geofield Proximity Source plugin manager.
*/
class GeofieldProximitySourceManager extends DefaultPluginManager {
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/GeofieldProximitySource', $namespaces, $module_handler, 'Drupal\geofield\Plugin\GeofieldProximitySourceInterface', 'Drupal\geofield\Annotation\GeofieldProximitySource');
$this->alterInfo('geofield_geofield_proximity_source_info');
$this->setCacheBackend($cache_backend, 'geofield_geofield_proximity_source_plugins');
}
/**
* Builds the common elements of the Proximity Form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $options
* The form options.
* @param string $context
* The array list of the specific view handler plugin type to look for.
* Possible values:
* - filter
* - sort
* - field
* - NULL (all).
*/
public function buildCommonFormElements(array &$form, FormStateInterface $form_state, array $options, $context = NULL) {
$user_input = $form_state->getUserInput();
// Attach Geofield Libraries.
$form['#attached']['library'][] = 'geofield/geofield_general';
$form['units'] = [
'#type' => 'select',
'#title' => $this->t('Unit of Measure'),
'#description' => '',
'#options' => geofield_radius_options(),
'#default_value' => '',
'#weight' => -10,
];
// In case of Proximity Filter settings, add an option to Expose Units in
// the Exposed Filter form.
if ($context == 'filter') {
$form['exposed_units'] = [
'#type' => 'checkbox',
'#title' => $this->t('Expose Units in the Exposed Filter form'),
'#default_value' => $user_input['options']['exposed_units'] ?? $options['exposed_units'],
'#weight' => -9,
];
}
$form['source_intro'] = [
'#markup' => $this->t('How do you want to enter your proximity parameters (distance and origin point)?'),
];
$form['source'] = [
'#type' => 'select',
'#title' => $this->t('Proximity Definition Mode (Source of Distance and Origin Point)'),
'#options' => [],
'#default_value' => '',
'#ajax' => [
'callback' => [get_class($this), 'sourceUpdate'],
'effect' => 'fade',
],
];
foreach ($this->getDefinitions() as $plugin_id => $definition) {
if (isset($definition['context'])
&& (empty($definition['context']) || in_array($context, $definition['context']))
&& (!isset($definition['exposedOnly']) || ($definition['exposedOnly'] && (isset($options['exposed']) && $options['exposed'])))
&& (!isset($definition['no_ui']) || !$definition['no_ui'])
) {
$form['source']['#options'][$plugin_id] = $definition['label'];
}
}
$form['source_configuration'] = [
'#type' => 'container',
'#tree' => TRUE,
'#prefix' => '<div id="proximity-source-configuration">',
'#suffix' => '</div>',
];
}
/**
* Ajax callback triggered on Source Selection.
*
* @param array $form
* The build form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* Ajax response with updated form element.
*/
public static function sourceUpdate(array $form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new ReplaceCommand(
'#proximity-source-configuration',
$form['options']['source_configuration']
));
return $response;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Drupal\geofield\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Validation constraint for geospatial values.
*
* @Constraint(
* id = "GeoType",
* label = @Translation("Geo data valid for geofield type.", context = "Validation"),
* )
*/
class GeoConstraint extends Constraint {
/**
* Message for invalid value.
*
* @var string
*/
public $message = '"@value" is not a valid geospatial content.';
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Drupal\geofield\Plugin\Validation\Constraint;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\geofield\GeoPHP\GeoPHPInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the GeoType constraint.
*/
class GeoConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
/**
* The geoPhpWrapper service.
*
* @var \Drupal\geofield\GeoPHP\GeoPHPInterface
*/
protected $geoPhpWrapper;
/**
* Constructs a new GeoConstraintValidator object.
*
* @param \Drupal\geofield\GeoPHP\GeoPHPInterface $geophp_wrapper
* The geoPhpWrapper.
*/
public function __construct(GeoPHPInterface $geophp_wrapper) {
$this->geoPhpWrapper = $geophp_wrapper;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('geofield.geophp')
);
}
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
if (isset($value)) {
$valid_geometry = TRUE;
try {
if (!$this->geoPhpWrapper->load($value)) {
$valid_geometry = FALSE;
}
}
catch (\Exception $e) {
$valid_geometry = FALSE;
}
if (!$valid_geometry) {
$this->context->addViolation($constraint->message, ['@value' => $value]);
}
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Drupal\geofield\Plugin\diff\Field;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\diff\Plugin\diff\Field\CoreFieldBuilder;
/**
* Plugin to compare the latitude and longitude for geofields.
*
* @FieldDiffBuilder(
* id = "geofield_field_diff_builder",
* label = @Translation("Geofield Field Diff"),
* field_types = {
* "geofield"
* },
* )
*/
class GeofieldFieldBuilder extends CoreFieldBuilder {
/**
* {@inheritdoc}
*/
public function build(FieldItemListInterface $field_items): array {
$result = [];
foreach ($field_items as $field_key => $field_item) {
if (!$field_item->isEmpty()) {
$value = $field_item->view([
'label' => 'hidden',
'type' => 'geofield_latlon',
]);
$rendered_value = $this->renderer->renderPlain($value);
$result[$field_key][] = $rendered_value;
}
}
return $result;
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Drupal\geofield\Plugin\migrate\field;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
/**
* MigrateField Plugin for Drupal 6 and 7 email fields.
*
* @MigrateField(
* id = "geofield",
* core = {7},
* type_map = {
* "geofield" = "geofield"
* },
* source_module = "geofield",
* destination_module = "geofield"
* )
*/
class Geofield extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function getFieldWidgetMap() {
return [
'geofield_wkt' => 'geofield_default',
'geofield_geojson' => 'geofield_default',
'geofield_kml' => 'geofield_default',
'geofield_gpx' => 'geofield_default',
'geofield_geohash' => 'geofield_default',
'geofield_latlon' => 'geofield_latlon',
'geofield_lat' => 'geofield_default',
'geofield_lon' => 'geofield_default',
'geofield_geo_type' => 'geofield_default',
'geofield_def_list' => 'geofield_default',
'geofield_description' => 'geofield_default',
'geofield_openlayers' => 'geofield_default',
];
}
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return [
'geofield_map_map' => 'geofield_default',
'geofield_wkt' => 'geofield_default',
'geofield_latlon' => 'geofield_latlon',
'geofield_geojson' => 'geofield_default',
'geofield_openlayers' => 'geofield_default',
];
}
/**
* {@inheritdoc}
*/
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'geofield_d7d8',
'source' => $field_name,
];
$migration->mergeProcessOfProperty($field_name, $process);
}
/**
* {@inheritdoc}
*/
public function processFieldValues(MigrationInterface $migration, $field_name, $data) {
$this->defineValueProcessPipeline($migration, $field_name, $data);
}
/**
* {@inheritdoc}
*/
public function alterFieldMigration(MigrationInterface $migration) {
$settings = [
'geofield' => [
'plugin' => 'geofield_field_settings',
],
];
$migration->mergeProcessOfProperty('settings', $settings);
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Drupal\geofield\Plugin\migrate\process;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\geofield\GeoPHP\GeoPHPInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Maps D7 geofield values to new the geofield values.
*
* @MigrateProcessPlugin(
* id = "geofield_d7d8"
* )
*/
class GeoField extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* The geoPhpWrapper service.
*
* @var \Drupal\geofield\GeoPHP\GeoPHPInterface
*/
protected $geoPhpWrapper;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, GeoPHPInterface $geo_php) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->geoPhpWrapper = $geo_php;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('geofield.geophp')
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
return [
'value' => $this->toWtk($value['geom']),
'geo_type' => $value['geo_type'],
'lat' => $value['lat'],
'lon' => $value['lon'],
'left' => $value['left'],
'top' => $value['top'],
'right' => $value['right'],
'bottom' => $value['bottom'],
'geohash' => $value['geohash'],
];
}
/**
* Convert geometric data to WTK format.
*
* @param string $geom
* The geometric data.
*
* @return string
* The geo data in WKT format.
*/
protected function toWtk($geom) {
$geometry = $this->geoPhpWrapper->load($geom);
if ($geometry instanceof \Geometry) {
return $geometry->out('wkt');
}
return '';
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Drupal\geofield\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Configure field instance settings for geofield.
*
* @MigrateProcessPlugin(
* id = "geofield_field_settings"
* )
*/
class GeoFieldFieldSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if ($row->getSourceProperty('type') == 'geofield' && isset($value['backend'])) {
$value['backend'] = ($value['backend'] != 'default') ? $value['backend'] : 'geofield_backend_default';
}
return $value;
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Drupal\geofield\Plugin\migrate\process;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\geofield\WktGeneratorInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Migrate latitude & longitude single values into a Geofield.
*
* The "geofield_latlon" process plugin transforms pairs of
* latitude & longitude single values into Geofield WKT format value.
*
* Example:
*
* @code
* process:
* field_geofield:
* plugin: geofield_latlon
* source:
* - latitude
* - longitude
*
* @endcode
*
* @MigrateProcessPlugin(
* id = "geofield_latlon"
* )
*/
class GeofieldLatLon extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* The WktGenerator service.
*
* @var \Drupal\geofield\WktGeneratorInterface
*/
protected $wktGenerator;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, WktGeneratorInterface $wkt_generator) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->wktGenerator = $wkt_generator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('geofield.wkt_generator')
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$value = array_map('floatval', $value);
[$lat, $lon] = $value;
if (empty($lat) && empty($lon)) {
return NULL;
}
return $this->wktGenerator->WktBuildPoint([$lon, $lat]);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Drupal\geofield\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Process WKT string and return the value for the Drupal Geofield.
*
* @MigrateProcessPlugin(
* id = "geofield_wkt"
* )
*
* Note: As remarked in issue #3074552
* this Migrate process plugin doesn't perform any transformation of
* source values. It just takes the value and returns it.
* So it is redundant with the Drupal core's get plugin, which just takes the
* source value as-is and inserts it into the field
*
* In other words, these following are equivalent:
*
* process:
* my_geofield:
* plugin: geofield_wkt
* source: my_wkt_source
*
* process:
* my_geofield: my_wkt_source
*
* This is kept as a placeholder, just in case any additional logic needs to
* be added in the future.
*/
class GeofieldWKT extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
return $value;
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Drupal\geofield\Plugin\views;
/**
* Generates a GeofieldBoundaryHandlerTrait.
*/
trait GeofieldBoundaryHandlerTrait {
/**
* Gets the query fragment for adding a boundary field to a query.
*
* @param string $table_name
* The proximity table name.
* @param string $field_id
* The proximity field ID.
* @param string $filter_lat_north_east
* The latitude to filter for.
* @param string $filter_lon_north_east
* The longitude to filter for.
* @param string $filter_lat_south_west
* The latitude to filter for.
* @param string $filter_lon_south_west
* The longitude to filter for.
*
* @return string
* The fragment to enter to actual query.
*/
public static function getBoundaryQueryFragment($table_name, $field_id, $filter_lat_north_east, $filter_lon_north_east, $filter_lat_south_west, $filter_lon_south_west) {
// Define the field name.
$field_lat = "{$table_name}.{$field_id}_lat";
$field_lon = "{$table_name}.{$field_id}_lon";
/*
* Map shows a map, not a globe, therefore it will never flip over
* the poles, but it will move across -180°/+180° longitude.
* So latitude will always have north larger than south, but east not
* necessarily larger than west.
*/
return "($field_lat BETWEEN $filter_lat_south_west AND $filter_lat_north_east)
AND
(
($filter_lon_south_west < $filter_lon_north_east AND $field_lon BETWEEN $filter_lon_south_west AND $filter_lon_north_east)
OR
(
$filter_lon_south_west > $filter_lon_north_east AND (
$field_lon BETWEEN $filter_lon_south_west AND 180 OR $field_lon BETWEEN -180 AND $filter_lon_north_east
)
)
)
";
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Drupal\geofield\Plugin\views;
/**
* Trait class for Geofield Proximity View Handlers.
*/
trait GeofieldProximityHandlerTrait {
/**
* Add an Order By declaration to the View Query.
*
* @param string $order
* The order to be applied (ASC or DESC)
*/
public function addQueryOrderBy($order) {
$this->ensureMyTable();
$lat_alias = $this->realField . '_lat';
$lon_alias = $this->realField . '_lon';
/** @var \Drupal\views\Plugin\views\query\Sql $query */
$query = $this->query;
try {
/** @var \Drupal\geofield\Plugin\GeofieldProximitySourceInterface $source_plugin */
$source_plugin = $this->proximitySourceManager->createInstance($this->options['source'], $this->options['source_configuration']);
$source_plugin->setViewHandler($this);
$source_plugin->setUnits($this->options['units']);
if ($haversine_options = $source_plugin->getHaversineOptions()) {
$haversine_options['destination_latitude'] = $this->tableAlias . '.' . $lat_alias;
$haversine_options['destination_longitude'] = $this->tableAlias . '.' . $lon_alias;
$query->addOrderBy(NULL, geofield_haversine($haversine_options), $order, $this->tableAlias . '_' . $this->field);
}
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
}
}
}

View File

@@ -0,0 +1,230 @@
<?php
namespace Drupal\geofield\Plugin\views\argument;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Markup;
use Drupal\geofield\Plugin\GeofieldProximitySourceManager;
use Drupal\geofield\WktGenerator;
use Drupal\views\Plugin\views\argument\Formula;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Argument handler for geofield proximity.
*
* Argument format should be in the following format:
* "40.73,-73.93<=5mi" (defaults to km).
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("geofield_proximity_argument")
*/
class GeofieldProximityArgument extends Formula implements ContainerFactoryPluginInterface {
use LoggerChannelTrait;
/**
* The WktGenerator object.
*
* @var \Drupal\geofield\WktGenerator
*/
protected $wktGenerator;
/**
* The geofield proximity manager.
*
* @var \Drupal\geofield\Plugin\GeofieldProximitySourceManager
*/
protected $proximitySourceManager;
/**
* The Geofield Proximity Source Plugin.
*
* @var \Drupal\geofield\Plugin\GeofieldProximitySourceInterface
*/
protected $sourcePlugin;
/**
* The Unites property.
*
* @var array
*/
protected $units;
/**
* Get the decoded Unites.
*
* @return array
* The decoded units array.
*/
protected function decodeUnits() {
return [
'km' => [
'label' => $this->t('Kilometers'),
'value' => 'GEOFIELD_KILOMETERS',
],
'm' => [
'label' => $this->t('Meters'),
'value' => 'GEOFIELD_METERS',
],
'mi' => [
'label' => $this->t('Miles'),
'value' => 'GEOFIELD_MILES',
],
'yd' => [
'label' => $this->t('Yards'),
'value' => 'GEOFIELD_YARDS',
],
'ft' => [
'label' => $this->t('Feet'),
'value' => 'GEOFIELD_FEET',
],
'nmi' => [
'label' => $this->T('Nautical Miles'),
'value' => 'GEOFIELD_NAUTICAL_MILES',
],
];
}
/**
* Get the markup list of the Unites.
*
* @return string
* The markup list of the Unites.
*/
protected function unitsListMarkup() {
$markup = '';
foreach ($this->units as $k => $unit) {
$markup .= '<br><strong>' . $k . '</strong> (for ' . $unit['label'] . ')';
}
return $markup;
}
/**
* Constructs a Handler object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\geofield\WktGenerator $wkt_generator
* The WktGenerator object.
* @param \Drupal\geofield\Plugin\GeofieldProximitySourceManager $proximity_source_manager
* The Geofield Proximity Source manager service.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
WktGenerator $wkt_generator,
GeofieldProximitySourceManager $proximity_source_manager,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->wktGenerator = $wkt_generator;
$this->proximitySourceManager = $proximity_source_manager;
$this->units = $this->decodeUnits();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('geofield.wkt_generator'),
$container->get('plugin.manager.geofield_proximity_source')
);
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['description']['#markup'] .= $this->t('<br><u>Proximity format should be in the following format: <strong>"40.73,-73.93<=5[unit]"</strong></u>, where the operator might be also: ><br>and [unit] should be one of the following key value: @units_decodes.<br><u>Note:</u> Use dot (.) as decimal separator, and not comma (,), otherwise results won\'t be accurate.', [
'@units_decodes' => Markup::create($this->unitsListMarkup()),
]);
}
/**
* {@inheritdoc}
*/
public function query($group_by = FALSE) {
$this->ensureMyTable();
$lat_alias = $this->realField . '_lat';
$lon_alias = $this->realField . '_lon';
try {
/** @var \Drupal\geofield\Plugin\GeofieldProximitySourceInterface $source_plugin */
$values = $this->getParsedReferenceLocation();
if (!empty($values)) {
$source_configuration = [
'origin' => [
'lat' => $values['lat'],
'lon' => $values['lon'],
],
];
$this->sourcePlugin = $this->proximitySourceManager->createInstance('geofield_context_filter', $source_configuration);
$this->sourcePlugin->setViewHandler($this);
$this->sourcePlugin->setUnits($values['units']);
if ($haversine_options = $this->sourcePlugin->getHaversineOptions()) {
$haversine_options['destination_latitude'] = $this->tableAlias . '.' . $lat_alias;
$haversine_options['destination_longitude'] = $this->tableAlias . '.' . $lon_alias;
$this->operator($haversine_options, $values['distance'], $values['operator']);
}
}
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
}
}
/**
* {@inheritdoc}
*/
protected function operator($options, $distance, $operator) {
if (!empty($distance) && is_numeric($distance)) {
/** @var \Drupal\views\Plugin\views\query\Sql $query */
$query = $this->query;
$query->addWhereExpression(0, geofield_haversine($options) . ' ' . $operator . ' ' . $distance);
}
}
/**
* Processes the passed argument into an array of relevant geolocation data.
*
* @return array|bool
* The calculated values.
*/
public function getParsedReferenceLocation() {
// Process argument values into an array.
preg_match('/^([0-9\-.]+),+([0-9\-.]+)([<>=]+)([0-9.]+)(.*$)/', trim((string) $this->getValue()), $values);
// Validate and return the passed argument.
return is_array($values) && !empty($values) ? [
'lat' => (isset($values[1]) && is_numeric($values[1]) && $values[1] >= -90 && $values[1] <= 90) ? floatval($values[1]) : FALSE,
'lon' => (isset($values[2]) && is_numeric($values[2]) && $values[2] >= -180 && $values[2] <= 180) ? floatval($values[2]) : FALSE,
'operator' => (isset($values[3]) && in_array($values[3], [
'<>',
'=',
'>=',
'<=',
'>',
'<',
])) ? $values[3] : '<=',
'distance' => (isset($values[4])) ? floatval($values[4]) : FALSE,
'units' => (isset($values[5]) && array_key_exists($values[5], $this->units)) ? $this->units[$values[5]]['value'] : 'GEOFIELD_KILOMETERS',
] : FALSE;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Drupal\geofield\Plugin\views\argument;
use Drupal\Core\Form\FormStateInterface;
use Drupal\geofield\Plugin\views\GeofieldBoundaryHandlerTrait;
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
use Drupal\views\Plugin\views\query\Sql;
/**
* Argument handler for geofield rectangular boundary.
*
* Argument format should be in the following format:
* NE-Lat,NE-Lng,SW-Lat,SW-Lng, so "11.1,33.3,55.5,77.7".
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("geofield_rectangular_boundary_argument")
*/
class GeofieldRectBoundaryArgument extends ArgumentPluginBase {
use GeofieldBoundaryHandlerTrait;
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['description']['#markup'] .= $this->t('<br>Boundary format should be in a NE-Latitude,NE-Longitude,SW-Latitude,SW-Longitude format: <strong>"40.773,-73.972,40.748,-73.984"</strong> .');
}
/**
* {@inheritdoc}
*/
public function query($group_by = FALSE) {
$values = $this->getParsedBoundary();
if (!($this->query instanceof Sql)) {
return;
}
if (empty($values)) {
return;
}
// Get the field alias.
$lat_north_east = $values['lat_north_east'];
$lng_north_east = $values['lng_north_east'];
$lat_south_west = $values['lat_south_west'];
$lng_south_west = $values['lng_south_west'];
if (
!is_numeric($lat_north_east)
|| !is_numeric($lng_north_east)
|| !is_numeric($lat_south_west)
|| !is_numeric($lng_south_west)
) {
return;
}
$this->query->addWhereExpression(
$group_by,
self::getBoundaryQueryFragment($this->ensureMyTable(), $this->realField, $lat_north_east, $lng_north_east, $lat_south_west, $lng_south_west)
);
}
/**
* Processes the passed argument into an array of relevant geolocation data.
*
* @return array|bool
* The calculated values.
*/
public function getParsedBoundary() {
// Cache the vales so this only gets processed once.
static $values;
if (!isset($values)) {
// Process argument values into an array.
preg_match('/^([0-9\-.]+),+([0-9\-.]+),+([0-9\-.]+),+([0-9\-.]+)(.*$)/', $this->getValue(), $values);
// Validate and return the passed argument.
$values = is_array($values) ? [
'lat_north_east' => (isset($values[1]) && is_numeric($values[1]) && $values[1] >= -90 && $values[1] <= 90) ? floatval($values[1]) : FALSE,
'lng_north_east' => (isset($values[2]) && is_numeric($values[2]) && $values[2] >= -180 && $values[2] <= 180) ? floatval($values[2]) : FALSE,
'lat_south_west' => (isset($values[2]) && is_numeric($values[3]) && $values[3] >= -90 && $values[3] <= 90) ? floatval($values[3]) : FALSE,
'lng_south_west' => (isset($values[2]) && is_numeric($values[4]) && $values[4] >= -180 && $values[4] <= 180) ? floatval($values[4]) : FALSE,
] : FALSE;
}
return $values;
}
}

View File

@@ -0,0 +1,193 @@
<?php
namespace Drupal\geofield\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\geofield\Plugin\GeofieldProximitySourceManager;
use Drupal\geofield\Plugin\views\GeofieldProximityHandlerTrait;
use Drupal\views\Plugin\views\field\NumericField;
use Drupal\views\ResultRow;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Field handler to render a Geofield proximity in Views.
*
* @ingroup views_field_handlers
*
* @ViewsField("geofield_proximity_field")
*/
class GeofieldProximityField extends NumericField {
use GeofieldProximityHandlerTrait;
use LoggerChannelTrait;
/**
* The geofield proximity manager.
*
* @var \Drupal\geofield\Plugin\GeofieldProximitySourceManager
*/
protected $proximitySourceManager;
/**
* The Geofield Proximity Source Plugin.
*
* @var \Drupal\geofield\Plugin\GeofieldProximitySourceInterface
*/
protected $sourcePlugin;
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['units'] = ['default' => 'GEOFIELD_KILOMETERS'];
// Data sources and info needed.
$options['source'] = ['default' => 'geofield_manual_origin'];
$options['source_configuration'] = ['default' => []];
return $options;
}
/**
* Constructs the GeofieldProximityField object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\geofield\Plugin\GeofieldProximitySourceManager $proximity_source_manager
* The Geofield Proximity Source manager service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, GeofieldProximitySourceManager $proximity_source_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->proximitySourceManager = $proximity_source_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.geofield_proximity_source')
);
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$context = $this->pluginDefinition['plugin_type'];
$user_input = $form_state->getUserInput();
$source_plugin_id = $user_input['options']['source'] ?? $this->options['source'];
$source_plugin_configuration = $user_input['options']['source_configuration'] ?? $this->options['source_configuration'];
$this->proximitySourceManager->buildCommonFormElements($form, $form_state, $this->options, $context);
$form['units']['#default_value'] = $this->options['units'];
$form['source']['#default_value'] = $this->options['source'];
try {
$this->sourcePlugin = $this->proximitySourceManager->createInstance($source_plugin_id, $source_plugin_configuration);
$this->sourcePlugin->setViewHandler($this);
$form['source_configuration']['origin_description'] = [
'#markup' => $this->sourcePlugin->getPluginDefinition()['description'],
'#weight' => -10,
];
$this->sourcePlugin->buildOptionsForm($form['source_configuration'], $form_state, ['source_configuration']);
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
}
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
parent::validateOptionsForm($form, $form_state);
try {
$this->sourcePlugin->validateOptionsForm($form['source_configuration'], $form_state, ['source_configuration']);
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
$form_state->setErrorByName($form['source'], $this->t("The Proximity Source couldn't be set due to: @error", [
'@error' => $e,
]));
}
}
/**
* {@inheritdoc}
*/
public function getValue(ResultRow $values, $field = NULL) {
try {
$this->sourcePlugin = $this->proximitySourceManager->createInstance($this->options['source'], $this->options['source_configuration']);
$this->sourcePlugin->setViewHandler($this);
$this->sourcePlugin->setUnits($this->options['units']);
return $this->sourcePlugin->getProximity($values->{$this->aliases['latitude']}, $values->{$this->aliases['longitude']});
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
return NULL;
}
}
/**
* {@inheritdoc}
*/
public function query() {
$this->ensureMyTable();
$this->addAdditionalFields();
}
/**
* {@inheritdoc}
*/
public function clickSort($order) {
$this->addQueryOrderBy($order);
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
$build = '';
$value = $this->getValue($values);
if (is_numeric($value)) {
try {
$build = parent::render($values);
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
}
}
return $build;
}
/**
* {@inheritdoc}
*/
public function adminSummary() {
$output = parent::adminSummary();
return $this->options['source'] . ' - ' . $output;
}
}

View File

@@ -0,0 +1,649 @@
<?php
namespace Drupal\geofield\Plugin\views\filter;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Render\RendererInterface;
use Drupal\geofield\Plugin\GeofieldProximitySourceManager;
use Drupal\views\Plugin\views\filter\NumericFilter;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Field handler to filter Geofields by proximity.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("geofield_proximity_filter")
*/
class GeofieldProximityFilter extends NumericFilter {
use LoggerChannelTrait;
/**
* The Renderer service property.
*
* @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
*/
protected $renderer;
/**
* The geofield proximity manager.
*
* @var \Drupal\geofield\Plugin\GeofieldProximitySourceManager
*/
protected $proximitySourceManager;
/**
* The Geofield Radius Options.
*
* @var array
*/
protected $geofieldRadiusOptions;
/**
* The Geofield Proximity Source Plugin.
*
* @var \Drupal\geofield\Plugin\GeofieldProximitySourceInterface
*/
protected $sourcePlugin;
/**
* The current request.
*
* @var null|\Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The Value Label.
*
* @var string
*/
protected $valueLabel;
/**
* The Min Label.
*
* @var string
*/
protected $minLabel;
/**
* The Max Label.
*
* @var string
*/
protected $maxLabel;
/**
* The Origin Label.
*
* @var string
*/
protected $originLabel;
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
// Override some default settings from the NumericFilter.
$options['operator'] = ['default' => '<='];
$options['units'] = ['default' => 'GEOFIELD_KILOMETERS'];
$options['exposed_units'] = [
'default' => FALSE,
];
// Default Data sources Info.
$options['source'] = ['default' => 'geofield_manual_origin'];
$options['source_configuration'] = [
'default' => [
'exposed_summary' => TRUE,
],
];
return $options;
}
/**
* Constructs the GeofieldProximityFilter object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
* @param \Drupal\geofield\Plugin\GeofieldProximitySourceManager $proximity_source_manager
* The Geofield Proximity Source manager service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
RendererInterface $renderer,
GeofieldProximitySourceManager $proximity_source_manager,
RequestStack $request_stack,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->renderer = $renderer;
$this->proximitySourceManager = $proximity_source_manager;
$this->geofieldRadiusOptions = geofield_radius_options();
$this->request = $request_stack;
$this->valueLabel = $this->t('Distance');
$this->minLabel = $this->t('Min');
$this->maxLabel = $this->t('Max');
$this->originLabel = $this->t('Origin');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('renderer'),
$container->get('plugin.manager.geofield_proximity_source'),
$container->get('request_stack')
);
}
/**
* Provide Operators List.
*/
public function operators() {
$operators = [
'<' => [
'title' => $this->t('Is less than'),
'method' => 'opSimple',
'short' => $this->t('<'),
'values' => 1,
],
'<=' => [
'title' => $this->t('Is less than or equal to'),
'method' => 'opSimple',
'short' => $this->t('<='),
'values' => 1,
],
'=' => [
'title' => $this->t('Is equal to'),
'method' => 'opSimple',
'short' => $this->t('='),
'values' => 1,
],
'!=' => [
'title' => $this->t('Is not equal to'),
'method' => 'opSimple',
'short' => $this->t('!='),
'values' => 1,
],
'>=' => [
'title' => $this->t('Is greater than or equal to'),
'method' => 'opSimple',
'short' => $this->t('>='),
'values' => 1,
],
'>' => [
'title' => $this->t('Is greater than'),
'method' => 'opSimple',
'short' => $this->t('>'),
'values' => 1,
],
'between' => [
'title' => $this->t('Is between'),
'method' => 'opBetween',
'short' => $this->t('between'),
'values' => 2,
],
'not between' => [
'title' => $this->t('Is not between'),
'method' => 'opBetween',
'short' => $this->t('not between'),
'values' => 2,
],
];
return $operators;
}
/**
* {@inheritdoc}
*/
public function query() {
$this->ensureMyTable();
$lat_alias = $this->realField . '_lat';
$lon_alias = $this->realField . '_lon';
try {
/** @var \Drupal\geofield\Plugin\GeofieldProximitySourceInterface $source_plugin */
$this->sourcePlugin = $this->proximitySourceManager->createInstance($this->options['source'], $this->options['source_configuration']);
$this->sourcePlugin->setViewHandler($this);
$this->sourcePlugin->setUnits($this->options['units']);
$info = $this->operators();
// Add query condition in case of valid proximity filter options.
if ($haversine_options = $this->sourcePlugin->getHaversineOptions()) {
$haversine_options['destination_latitude'] = $this->tableAlias . '.' . $lat_alias;
$haversine_options['destination_longitude'] = $this->tableAlias . '.' . $lon_alias;
$this->{$info[$this->operator]['method']}($haversine_options);
// Ensure that destination is valid.
$condition = (new Condition('AND'))->isNotNull($haversine_options['destination_latitude'])->isNotNull($haversine_options['destination_longitude']);
$this->query->addWhere(0, $condition);
}
// Otherwise output empty result in case of unexposed proximity filter.
elseif (!$this->isExposed()) {
// Origin is not valid so return no results (if not exposed filter).
$this->query->addWhereExpression($this->options['group'], '1=0');
}
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
}
}
/**
* {@inheritdoc}
*/
protected function opBetween($field) {
if (!empty($this->value['min']) && is_numeric($this->value['min']) &&
!empty($this->value['max']) && is_numeric($this->value['max'])) {
// Be sure to convert $options into array,
// as this method PhpDoc might expect $options to be an object.
$field = (array) $field;
$this->query->addWhereExpression($this->options['group'], geofield_haversine($field) . ' ' . strtoupper($this->operator) . ' ' . $this->value['min'] . ' AND ' . $this->value['max']);
}
}
/**
* {@inheritdoc}
*/
protected function opSimple($field) {
if (!empty($this->value['value']) && is_numeric($this->value['value'])) {
// Be sure to convert $options into array,
// as this method PhpDoc might expect $options to be an object.
$field = (array) $field;
$this->query->addWhereExpression($this->options['group'], geofield_haversine($field) . ' ' . $this->operator . ' ' . $this->value['value']);
}
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$context = $this->pluginDefinition['plugin_type'];
$user_input = $form_state->getUserInput();
$source_plugin_id = $user_input['options']['source'] ?? $this->options['source'];
$source_plugin_configuration = $user_input['options']['source_configuration'] ?? $this->options['source_configuration'];
$this->proximitySourceManager->buildCommonFormElements($form, $form_state, $this->options, $context);
$form['units']['#default_value'] = $user_input['options']['units'] ?? $this->options['units'];
$form['source']['#default_value'] = $source_plugin_id;
$form['source_configuration']['exposed_summary'] = [
'#type' => 'checkbox',
'#title' => $this->t('Expose Summary Description for the specific Proximity Filter Source'),
'#default_value' => $user_input['options']['source_configuration']['exposed_summary'] ?? $this->options['source_configuration']['exposed_summary'],
'#states' => [
'visible' => [
':input[name="options[expose_button][checkbox][checkbox]"]' => ['checked' => TRUE],
],
],
];
try {
$this->sourcePlugin = $this->proximitySourceManager->createInstance($source_plugin_id, $source_plugin_configuration);
$this->sourcePlugin->setViewHandler($this);
$form['source_configuration']['origin_description'] = [
'#markup' => $this->sourcePlugin->getPluginDefinition()['description'],
'#weight' => -10,
];
$this->sourcePlugin->buildOptionsForm($form['source_configuration'], $form_state, ['source_configuration']);
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
}
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
parent::validateOptionsForm($form, $form_state);
try {
$this->sourcePlugin->validateOptionsForm($form['source_configuration'], $form_state, ['source_configuration']);
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
$form_state->setErrorByName($form['source'], $this->t("The Proximity Source couldn't be set due to: @error", [
'@error' => $e,
]));
}
}
/**
* {@inheritdoc}
*/
public function validateExposed(&$form, FormStateInterface $form_state) {
parent::validateExposed($form, $form_state);
$form_values = $form_state->getValues();
$identifier = $this->options['expose']['identifier'];
$identifier_operator = $form_values[$identifier . '_op'] ?? NULL;
$which = isset($identifier_operator) && in_array($identifier_operator, $this->operatorValues(2)) ? 'minmax' : 'value';
// Set/alter the Unit value, if present in the form option.
if (isset($form_values["field_geofield_proximity"]["unit"])) {
$this->options["units"] = $form_values["field_geofield_proximity"]["unit"];
}
// Validate the Distance field.
if ($which !== 'minmax' && isset($form_values[$identifier]['value']) && (!empty($form_values[$identifier]['value']) && !is_numeric($form_values[$identifier]['value']))) {
$form_state->setError($form[$identifier . '_wrapper'][$identifier]['value'], $this->t('The @value_label value is not valid.', [
'@value_label' => $this->valueLabel,
]));
}
// Validate the Distance field as positive value.
if ($which !== 'minmax' && !empty($form_values[$identifier]['value']) && $form_values[$identifier]['value'] < 0) {
$form_state->setError($form[$identifier . '_wrapper'][$identifier]['value'], $this->t('The @value_label value should be positive.', [
'@value_label' => $this->valueLabel,
]));
}
// Validate the Min value.
if ($which !== 'value' && !empty($form_values[$identifier]['min']) && !is_numeric($form_values[$identifier]['min'])) {
$form_state->setError($form[$identifier . '_wrapper'][$identifier]['min'], $this->t('The @min_label value is not valid.', [
'@min_label' => $this->minLabel,
]));
}
// Validate the Max value.
if ($which !== 'value' && !empty($form_values[$identifier]['max']) && !is_numeric($form_values[$identifier]['max'])) {
$form_state->setError($form[$identifier . '_wrapper'][$identifier]['max'], $this->t('The @max_label value is not valid.', [
'@max_label' => $this->maxLabel,
]));
}
// Validate the Min value as positive value.
if ($which !== 'value' && !empty($form_values[$identifier]['min']) && $form_values[$identifier]['min'] < 0) {
$form_state->setError($form[$identifier . '_wrapper'][$identifier]['min'], $this->t('The @min_label value should be positive.', [
'@min_label' => $this->minLabel,
]));
}
// Validate the Max value as positive value.
if ($which !== 'value' && !empty($form_values[$identifier]['max']) && $form_values[$identifier]['max'] < 0) {
$form_state->setError($form[$identifier . '_wrapper'][$identifier]['max'], $this->t('The @max_label value should be positive.', [
'@max_label' => $this->maxLabel,
]));
}
// Validate the Min and Max values relationship.
if ($which !== 'value' && !empty($form_values[$identifier]['min']) && isset($form_values[$identifier]['max'])
&& ($form_values[$identifier]['min'] > $form_values[$identifier]['max'])) {
$form_state->setError($form[$identifier . '_wrapper'][$identifier]['min'], $this->t('The @min_label value should be smaller than the @max_label value.', [
'@min_label' => $this->minLabel,
'@max_label' => $this->maxLabel,
]));
}
// Validate the Origin (not null) value, when the filter is required.
if ($this->options['expose']['required']) {
if (isset($form_values[$identifier]['source_configuration']['origin_address'])) {
$input_address = $form_values[$identifier]['source_configuration']['origin_address'];
if (empty($input_address)) {
$form_state->setError($form[$identifier . '_wrapper'][$identifier]['source_configuration']['origin_address'], $this->t('The @origin_label Address is required', [
'@origin_label' => $this->originLabel,
]));
}
}
elseif (isset($form_values[$identifier]['source_configuration']['origin'])) {
$input_origin = $form_values[$identifier]['source_configuration']['origin'];
if ($this->sourcePlugin->isEmptyLocation($input_origin['lat'], $input_origin['lon'])) {
$form_state->setError($form[$identifier . '_wrapper'][$identifier]['source_configuration']['origin'], $this->t('The @origin_label (Lat/Lon) is required', [
'@origin_label' => $this->originLabel,
]));
}
}
}
}
/**
* {@inheritdoc}
*/
public function valueForm(&$form, FormStateInterface $form_state) {
parent::valueForm($form, $form_state);
$form['value'] = [
'#tree' => TRUE,
];
$units_description = '';
$user_input = $form_state->getUserInput();
// We have to make some choices when creating this as an exposed
// filter form. For example, if the operator is locked and thus
// not rendered, we can't render dependencies; instead we only
// render the form items we need.
$which = 'all';
$source = !empty($form['operator']) ? ':input[name="options[operator]"]' : '';
if ($exposed = $form_state->get('exposed')) {
$identifier = $this->options['expose']['identifier'];
if (!isset($user_input[$identifier]) || !is_array($user_input[$identifier])) {
$user_input[$identifier] = [];
}
if (isset($this->options["exposed_units"]) && !$this->options["exposed_units"]) {
$units_description = $this->t('Units: @units', [
'@units' => isset($user_input['options']['units']) ? $this->geofieldRadiusOptions[$user_input['options']['units']] : $this->geofieldRadiusOptions[$this->options['units']],
]);
}
if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
// Exposed and locked.
$which = in_array($this->operator, $this->operatorValues(2)) ? 'minmax' : 'value';
}
else {
$source = ':input[name="' . $this->options['expose']['operator_id'] . '"]';
}
}
if ($which == 'all' || $which == 'value') {
$form['value']['value'] = [
'#type' => 'textfield',
'#title' => $exposed && empty($source) ? $this->valueLabel . ' ' . $this->operator : (!$exposed ? $this->valueLabel : ''),
'#size' => 30,
'#default_value' => $this->value['value'],
'#description' => $exposed && isset($units_description) ? $units_description : '',
];
if (!empty($this->options['expose']['placeholder'])) {
$form['value']['value']['#attributes']['placeholder'] = $this->options['expose']['placeholder'];
}
if ($exposed && isset($identifier) && !isset($user_input[$identifier]['value'])) {
$user_input[$identifier]['value'] = $this->value['value'];
$form_state->setUserInput($user_input);
}
}
if ($which == 'all') {
// Setup #states for all operators with one value.
foreach ($this->operatorValues(1) as $operator) {
$form['value']['value']['#states']['visible'][] = [
$source => ['value' => $operator],
];
}
}
if ($which == 'all' || $which == 'minmax') {
$form['value']['min'] = [
'#type' => 'textfield',
'#title' => $exposed && empty($source) ? $this->valueLabel . ' ' . $this->operator . ' ' . $this->minLabel : (!$exposed ? $this->minLabel : $this->minLabel),
'#size' => 30,
'#default_value' => $this->value['min'],
'#description' => $exposed ? $units_description : '',
];
if (!empty($this->options['expose']['min_placeholder'])) {
$form['value']['min']['#attributes']['placeholder'] = $this->options['expose']['min_placeholder'];
}
$form['value']['max'] = [
'#type' => 'textfield',
'#title' => $this->maxLabel,
'#size' => 30,
'#default_value' => $this->value['max'],
'#description' => $exposed ? $units_description : '',
];
if (!empty($this->options['expose']['max_placeholder'])) {
$form['value']['max']['#attributes']['placeholder'] = $this->options['expose']['max_placeholder'];
}
if ($which == 'all') {
$states = [];
// Setup #states for all operators with two values.
foreach ($this->operatorValues(2) as $operator) {
$states['#states']['visible'][] = [
$source => ['value' => $operator],
];
}
$form['value']['min'] = array_merge((array) $form['value']['min'], $states);
$form['value']['max'] = array_merge((array) $form['value']['max'], $states);
}
if ($exposed && isset($identifier) && !isset($user_input[$identifier]['min'])) {
$user_input[$identifier]['min'] = $this->value['min'];
}
if ($exposed && isset($identifier) && !isset($user_input[$identifier]['max'])) {
$user_input[$identifier]['max'] = $this->value['max'];
}
if (isset($identifier) && isset($form[$identifier . '_wrapper'])) {
unset($form[$identifier . '_wrapper'][$identifier . '_op']['#title_display']);
$form[$identifier . '_wrapper'][$identifier . '_op']['#title'] = $this->valueLabel;
}
if (!isset($form['value'])) {
// Ensure there is something in the 'value'.
$form['value'] = [
'#type' => 'value',
'#value' => NULL,
];
}
}
// Build the specific Geofield Proximity Form Elements.
if ($exposed && isset($identifier)) {
$form['value']['#type'] = 'fieldset';
// Expose the Units selector, if required.
if (isset($this->options["exposed_units"]) && $this->options["exposed_units"]) {
$form['value']['unit'] = [
'#type' => 'select',
'#options' => geofield_radius_options(),
'#default_value' => $user_input['options']['units'] ?? $this->options['units'],
];
}
$form['value']['source_configuration'] = [
'#type' => 'container',
];
try {
$source_plugin_id = $this->options['source'];
$source_plugin_configuration = isset($identifier) && isset($user_input[$identifier]['origin']) ? $user_input[$identifier] : $this->options['source_configuration'];
/** @var \Drupal\geofield\Plugin\GeofieldProximitySourceInterface $source_plugin */
$this->sourcePlugin = $this->proximitySourceManager->createInstance($source_plugin_id, $source_plugin_configuration);
$this->sourcePlugin->setViewHandler($this);
$proximity_origin = $this->sourcePlugin->getOrigin();
$this->sourcePlugin->buildOptionsForm($form['value']['source_configuration'], $form_state, ['source_configuration'], $exposed);
// Write the Proximity Filter exposed summary.
if ($this->options['source_configuration']['exposed_summary']) {
$form['value']['exposed_summary'] = $this->exposedSummary();
}
if (!isset($user_input[$identifier]['origin']) && !empty($proximity_origin)) {
$user_input[$identifier]['origin'] = [
'lat' => $proximity_origin['lat'],
'lon' => $proximity_origin['lon'],
];
$form_state->setUserInput($user_input);
}
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
$form_state->setErrorByName($form['value']['source_configuration'], $this->t("The Proximity Source couldn't be set due to: @error", [
'@error' => $e,
]));
}
}
}
/**
* {@inheritdoc}
*/
public function acceptExposedInput($input) {
if (empty($this->options['exposed'])) {
return TRUE;
}
// Set the correct source configurations origin from exposed filter input
// coordinates.
$identifier = $this->options['expose']['identifier'];
if (!empty($input[$identifier]['source_configuration'])) {
foreach ($input[$identifier]['source_configuration'] as $k => $value) {
$this->options['source_configuration'][$k] = $input[$identifier]['source_configuration'][$k];
}
}
// The parent NumericFilter acceptExposedInput will care to correctly set
// the options value.
return parent::acceptExposedInput($input);
}
/**
* {@inheritdoc}
*/
public function adminSummary() {
$output = parent::adminSummary();
return $this->options['source'] . ' ' . $output;
}
/**
* Expose a Summary.
*/
protected function exposedSummary() {
try {
return [
'#type' => 'html_tag',
'#tag' => 'div',
"#value" => $this->sourcePlugin->getPluginDefinition()['description'],
"#attributes" => [
'class' => ['proximity-filter-summary'],
],
];
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
return NULL;
}
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace Drupal\geofield\Plugin\views\filter;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\geofield\Plugin\views\GeofieldBoundaryHandlerTrait;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
use Drupal\views\Plugin\views\query\Sql;
/**
* Filter handler for search keywords.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("geofield_rectangular_boundary_filter")
*/
class GeofieldRectBoundaryFilter extends FilterPluginBase implements ContainerFactoryPluginInterface {
use GeofieldBoundaryHandlerTrait;
/**
* {@inheritdoc}
*/
protected $alwaysMultiple = TRUE;
/**
* {@inheritdoc}
*/
public function adminSummary() {
return $this->t("Rectangular Boundary filter");
}
/**
* {@inheritdoc}
*/
protected function valueForm(&$form, FormStateInterface $form_state) {
parent::valueForm($form, $form_state);
$form['value']['#tree'] = TRUE;
$form['value']['#prefix'] = '<div id="geofield-boundary-filter">';
$form['value']['#suffix'] = '</div>';
$form['value']['group'] = [
'#type' => 'details',
'#title' => $this->t('Rectangle Boundaries'),
'#open' => TRUE,
];
$value_element = &$form['value'];
// Add the Latitude and Longitude elements.
$value_element['group']['lat_north_east'] = [
'#type' => 'textfield',
'#title' => $this->t('NE Latitude'),
'#default_value' => !empty($this->value['group']['lat_north_east']) ? $this->value['group']['lat_north_east'] : '',
'#weight' => 10,
'#size' => 12,
];
$value_element['group']['lng_north_east'] = [
'#type' => 'textfield',
'#title' => $this->t('NE Longitude'),
'#default_value' => !empty($this->value['group']['lng_north_east']) ? $this->value['group']['lng_north_east'] : '',
'#weight' => 20,
'#size' => 12,
];
$value_element['group']['lat_south_west'] = [
'#type' => 'textfield',
'#title' => $this->t('SW Latitude'),
'#default_value' => !empty($this->value['group']['lat_south_west']) ? $this->value['group']['lat_south_west'] : '',
'#weight' => 30,
'#size' => 12,
];
$value_element['group']['lng_south_west'] = [
'#type' => 'textfield',
'#title' => $this->t('SW Longitude'),
'#default_value' => !empty($this->value['group']['lng_south_west']) ? $this->value['group']['lng_south_west'] : '',
'#weight' => 40,
'#size' => 12,
];
}
/**
* {@inheritdoc}
*/
public function query() {
if (!($this->query instanceof Sql)) {
return;
}
if (empty($this->value)) {
return;
}
// Get the field alias.
$lat_north_east = $this->value['group']['lat_north_east'];
$lng_north_east = $this->value['group']['lng_north_east'];
$lat_south_west = $this->value['group']['lat_south_west'];
$lng_south_west = $this->value['group']['lng_south_west'];
if (
!is_numeric($lat_north_east)
|| !is_numeric($lng_north_east)
|| !is_numeric($lat_south_west)
|| !is_numeric($lng_south_west)
) {
return;
}
$this->query->addWhereExpression(
$this->options['group'],
self::getBoundaryQueryFragment($this->ensureMyTable(), $this->realField, $lat_north_east, $lng_north_east, $lat_south_west, $lng_south_west)
);
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Drupal\geofield\Plugin\views\sort;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\geofield\Plugin\GeofieldProximitySourceManager;
use Drupal\geofield\Plugin\views\GeofieldProximityHandlerTrait;
use Drupal\views\Plugin\views\sort\SortPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Field handler to sort Geofields by proximity.
*
* @ingroup views_field_handlers
*
* @ViewsSort("geofield_proximity_sort")
*/
class GeofieldProximitySort extends SortPluginBase {
use GeofieldProximityHandlerTrait;
use LoggerChannelTrait;
/**
* The geofield proximity manager.
*
* @var \Drupal\geofield\Plugin\GeofieldProximitySourceManager
*/
protected $proximitySourceManager;
/**
* The Geofield Proximity Source Plugin.
*
* @var \Drupal\geofield\Plugin\GeofieldProximitySourceInterface
*/
protected $sourcePlugin;
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['units'] = ['default' => 'GEOFIELD_KILOMETERS'];
// Data sources and info needed.
$options['source'] = ['default' => 'geofield_manual_origin'];
$options['source_configuration'] = ['default' => []];
return $options;
}
/**
* Constructs the GeofieldProximitySort object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\geofield\Plugin\GeofieldProximitySourceManager $proximity_source_manager
* The Geofield Proximity Source manager service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, GeofieldProximitySourceManager $proximity_source_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->proximitySourceManager = $proximity_source_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.geofield_proximity_source')
);
}
/**
* {@inheritdoc}
*/
public function query() {
$this->addQueryOrderBy($this->options['order']);
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$context = $this->pluginDefinition['plugin_type'];
$user_input = $form_state->getUserInput();
$source_plugin_id = $user_input['options']['source'] ?? $this->options['source'];
$source_plugin_configuration = $user_input['options']['source_configuration'] ?? $this->options['source_configuration'];
$this->proximitySourceManager->buildCommonFormElements($form, $form_state, $this->options, $context);
$form['units']['#default_value'] = $this->options['units'];
$form['source']['#default_value'] = $this->options['source'];
try {
$this->sourcePlugin = $this->proximitySourceManager->createInstance($source_plugin_id, $source_plugin_configuration);
$this->sourcePlugin->setViewHandler($this);
$form['source_configuration']['origin_description'] = [
'#markup' => $this->sourcePlugin->getPluginDefinition()['description'],
'#weight' => -10,
];
$this->sourcePlugin->buildOptionsForm($form['source_configuration'], $form_state, ['source_configuration']);
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
}
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
parent::validateOptionsForm($form, $form_state);
try {
$this->sourcePlugin->validateOptionsForm($form['source_configuration'], $form_state, ['source_configuration']);
}
catch (\Exception $e) {
$this->getLogger('geofield')->error($e->getMessage());
$form_state->setErrorByName($form['source'], $this->t("The Proximity Source couldn't be set due to: @error", [
'@error' => $e,
]));
}
}
/**
* {@inheritdoc}
*/
public function adminSummary() {
$output = parent::adminSummary();
return $this->options['source'] . ' - ' . $output;
}
}

View File

@@ -0,0 +1,336 @@
<?php
namespace Drupal\geofield;
/**
* Helper class that generates WKT format geometries.
*/
class WktGenerator implements WktGeneratorInterface {
/**
* Helper to generate DD coordinates.
*
* @param int $min
* The minimum value available to return.
* @param int $max
* The minimum value available to return.
* @param bool $int
* Force to return an integer value. Defaults to FALSE.
*
* @return float|int
* The coordinate component.
*/
protected function ddGenerate($min, $max, $int = FALSE) {
$func = 'rand';
if (function_exists('mt_rand')) {
$func = 'mt_rand';
}
$number = $func($min, $max);
if ($int || $number === $min || $number === $max) {
return $number;
}
$decimals = $func(1, pow(10, 5)) / pow(10, 5);
return round($number + $decimals, 5);
}
/**
* {@inheritdoc}
*/
public function wktGenerateGeometry() {
$types = [
GEOFIELD_TYPE_POINT,
GEOFIELD_TYPE_MULTIPOINT,
GEOFIELD_TYPE_LINESTRING,
GEOFIELD_TYPE_MULTILINESTRING,
GEOFIELD_TYPE_POLYGON,
GEOFIELD_TYPE_MULTIPOLYGON,
];
// Don't always generate the same type.
shuffle($types);
$type = $types[0];
$func = 'WktGenerate' . ucfirst($type);
if (method_exists($this, $func)) {
return $this->$func();
}
return 'POINT (0 0)';
}
/**
* Generates a random coordinates array.
*
* @return array
* A Lon, Lat array
*/
protected function randomPoint() {
$lon = $this->ddGenerate(-180, 180);
$lat = $this->ddGenerate(-84, 84);
return [$lon, $lat];
}
/**
* Generates a WKT string given a feature type and some coordinates.
*
* @param string $type
* The Geo feature type.
* @param string $value
* The coordinates to include.
*
* @return string
* The WKT value.
*/
protected function buildWkt($type, $value) {
return strtoupper($type) . ' (' . $value . ')';
}
/**
* Builds a multi-geometry coordinates string given an array of features.
*
* @param array $coordinates
* The coordinates to generate the multi-geometry.
*
* @return string
* The multi-geometry coordinates string.
*/
protected function buildMultiCoordinates(array $coordinates) {
return '(' . implode('), (', $coordinates) . ')';
}
/**
* Generates a point coordinates.
*
* @param array $point
* A Lon Lat array.
*
* @return string
* The structured point coordinates.
*/
protected function buildPoint(array $point) {
return implode(' ', $point);
}
/**
* {@inheritdoc}
*/
public function wktGeneratePoint(array $point = NULL) {
$point = $point ? $point : $this->randomPoint();
return $this->wktBuildPoint($point);
}
/**
* {@inheritdoc}
*/
public function wktBuildPoint(array $point) {
return $this->buildWkt(GEOFIELD_TYPE_POINT, $this->buildPoint($point));
}
/**
* Generates a multipoint coordinates.
*
* @return string
* The structured multipoint coordinates.
*/
protected function generateMultipoint() {
$num = $this->ddGenerate(1, 5, TRUE);
$start = $this->randomPoint();
$points[] = $this->buildPoint($start);
for ($i = 0; $i < $num; $i += 1) {
$diff = $this->randomPoint();
$start[0] += $diff[0] / 100;
$start[1] += $diff[1] / 100;
$points[] = $this->buildPoint($start);
}
return $this->buildMultiCoordinates($points);
}
/**
* {@inheritdoc}
*/
public function wktGenerateMultipoint() {
return $this->buildWkt(GEOFIELD_TYPE_MULTIPOINT, $this->generateMultipoint());
}
/**
* Generates a linestring components array.
*
* @param array $start
* The starting point. If not provided, will be randomly generated.
* @param int $segments
* Number of segments. If not provided, will be randomly generated.
*
* @return array
* The linestring components coordinates.
*/
protected function generateLinestring(array $start = NULL, $segments = NULL) {
$start = $start ? $start : $this->randomPoint();
$segments = $segments ? $segments : $this->ddGenerate(2, 5, TRUE);
$points[] = [$start[0], $start[1]];
// Points are at most 1km away from each other.
for ($i = 1; $i < $segments; $i += 1) {
$diff = $this->randomPoint();
$start[0] += $diff[0] / 100;
$start[1] += $diff[1] / 100;
$points[] = [$start[0], $start[1]];
}
return $points;
}
/**
* {@inheritdoc}
*/
public function wktGenerateLinestring(array $start = NULL, $segments = NULL) {
return $this->wktBuildLinestring($this->generateLinestring($start, $segments));
}
/**
* Builds a Linestring format string from an array of point components.
*
* @param array $points
* Array containing the linestring component's coordinates.
*
* @return string
* The structured linestring coordinates.
*/
protected function buildLinestring(array $points) {
$components = [];
foreach ($points as $point) {
$components[] = $this->buildPoint($point);
}
return implode(", ", $components);
}
/**
* {@inheritdoc}
*/
public function wktBuildLinestring(array $points) {
return $this->buildWkt(GEOFIELD_TYPE_LINESTRING, $this->buildLinestring($points));
}
/**
* Generates a multilinestring coordinates.
*
* @return string
* The structured multilinestring coordinates.
*/
protected function generateMultilinestring() {
$start = $this->randomPoint();
$num = $this->ddGenerate(1, 3, TRUE);
$lines[] = $this->buildLinestring($this->generateLinestring($start));
for ($i = 0; $i < $num; $i += 1) {
$diff = $this->randomPoint();
$start[0] += $diff[0] / 100;
$start[1] += $diff[1] / 100;
$lines[] = $this->buildLinestring($this->generateLinestring($start));
}
return $this->buildMultiCoordinates($lines);
}
/**
* {@inheritdoc}
*/
public function wktGenerateMultilinestring() {
return $this->buildWkt(GEOFIELD_TYPE_MULTILINESTRING, $this->generateMultilinestring());
}
/**
* Generates a polygon components array.
*
* @param array $start
* The starting point. If not provided, will be randomly generated.
* @param int $segments
* Number of segments. If not provided, will be randomly generated.
*
* @return array
* The polygon components coordinates.
*/
protected function generatePolygon(array $start = NULL, $segments = NULL) {
$start = $start ?: $this->randomPoint();
$segments = $segments ?: $this->ddGenerate(2, 4, TRUE);
$poly = $this->generateLinestring($start, $segments);
// Close the polygon.
$poly[] = $start;
return $poly;
}
/**
* {@inheritdoc}
*/
public function wktGeneratePolygon(array $start = NULL, $segments = NULL) {
return $this->wktBuildPolygon($this->generatePolygon($start, $segments));
}
/**
* Builds a polygon format string from an array of point components.
*
* @param array $points
* Array containing the polygon components coordinates.
*
* @return string
* The structured polygon coordinates.
*/
protected function buildPolygon(array $points) {
$components = [];
foreach ($points as $point) {
$components[] = $this->buildPoint($point);
}
return '(' . implode(", ", $components) . ')';
}
/**
* {@inheritdoc}
*/
public function wktBuildPolygon(array $points) {
return $this->buildWkt(GEOFIELD_TYPE_POLYGON, $this->buildPolygon($points));
}
/**
* Generates a multipolygon coordinates.
*
* @return string
* The structured multipolygon coordinates.
*/
protected function generateMultipolygon() {
$start = $this->randomPoint();
$num = $this->ddGenerate(1, 5, TRUE);
$segments = $this->ddGenerate(2, 3, TRUE);
$poly[] = $this->buildPolygon($this->generatePolygon($start, $segments));
for ($i = 0; $i < $num; $i += 1) {
$diff = $this->randomPoint();
$start[0] += $diff[0] / 100;
$start[1] += $diff[1] / 100;
$poly[] = $this->buildPolygon($this->generatePolygon($start, $segments));
}
return $this->buildMultiCoordinates($poly);
}
/**
* {@inheritdoc}
*/
public function wktGenerateMultipolygon() {
return $this->buildWkt(GEOFIELD_TYPE_MULTIPOLYGON, $this->generateMultipolygon());
}
/**
* Builds a multipolygon coordinates.
*
* @param array $rings
* The array of polygon arrays.
*
* @return string
* The structured multipolygon coordinates.
*/
protected function buildMultipolygon(array $rings) {
$poly = [];
foreach ($rings as $ring) {
$poly[] = $this->buildPolygon($ring);
}
return $this->buildMultiCoordinates($poly);
}
/**
* {@inheritdoc}
*/
public function wktBuildMultipolygon(array $rings) {
return $this->buildWkt(GEOFIELD_TYPE_MULTIPOLYGON, $this->buildMultipolygon($rings));
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Drupal\geofield;
/**
* Defines an interface for WktGenerator.
*/
interface WktGeneratorInterface {
/**
* Helper to generate a random WKT string.
*
* Try to keeps values sane, no shape is more than 100km across.
*
* @return string
* The random WKT value.
*/
public function wktGenerateGeometry();
/**
* Returns a WKT format point feature given a point.
*
* @param array $point
* The point coordinates.
*
* @return string
* The WKT point feature.
*/
public function wktBuildPoint(array $point);
/**
* Returns a WKT format point feature.
*
* @param array $point
* A Lon Lat array. By default, create a random pair.
*
* @return string
* The WKT point feature.
*/
public function wktGeneratePoint(array $point = NULL);
/**
* Returns a WKT format multipoint feature.
*
* @return string
* The WKT multipoint feature.
*/
public function wktGenerateMultipoint();
/**
* Returns a WKT format linestring feature given an array of points.
*
* @param array $points
* The linestring components.
*
* @return string
* The WKT linestring feature.
*/
public function wktBuildLinestring(array $points);
/**
* Returns a WKT format linestring feature.
*
* @param array $start
* The starting point. If not provided, will be randomly generated.
* @param int $segments
* Number of segments. If not provided, will be randomly generated.
*
* @return string
* The WKT linestring feature.
*/
public function wktGenerateLinestring(array $start = NULL, $segments = NULL);
/**
* Returns a WKT format multilinestring feature.
*
* @return string
* The WKT multilinestring feature.
*/
public function wktGenerateMultilinestring();
/**
* Returns a WKT format polygon feature given an array of points.
*
* @param array $points
* The polygon components.
*
* @return string
* The WKT polygon feature.
*/
public function wktBuildPolygon(array $points);
/**
* Returns a WKT format polygon feature.
*
* @param array $start
* The starting point. If not provided, will be randomly generated.
* @param int $segments
* Number of segments. If not provided, will be randomly generated.
*
* @return string
* The WKT polygon feature.
*/
public function wktGeneratePolygon(array $start = NULL, $segments = NULL);
/**
* Returns a WKT format multipolygon feature given an array of polygon points.
*
* @param array $rings
* The array of polygon arrays.
*
* @return string
* The WKT multipolygon feature.
*/
public function wktBuildMultipolygon(array $rings);
/**
* Returns a WKT format multipolygon feature.
*
* @return string
* The WKT multipolygon feature.
*/
public function wktGenerateMultipolygon();
}