diff --git a/src/Plugin/views/argument/BBoxArgument.php b/src/Plugin/views/argument/BBoxArgument.php index d782aba..d1e34f0 100644 --- a/src/Plugin/views/argument/BBoxArgument.php +++ b/src/Plugin/views/argument/BBoxArgument.php @@ -4,6 +4,7 @@ namespace Drupal\views_geojson\Plugin\views\argument; use Drupal\views\Plugin\views\join\JoinPluginBase; use Drupal\views\Plugin\views\argument\StringArgument; +use Drupal\Core\Database\Query\Condition; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -25,6 +26,7 @@ class BBoxArgument extends StringArgument implements ContainerFactoryPluginInter protected function defineOptions() { $options = parent::defineOptions(); $options['round_coordinates'] = ['default' => TRUE]; + $options['bbox_wrap'] = ['default' => TRUE]; $options['point_logic'] = ['default' => TRUE]; return $options; @@ -52,9 +54,15 @@ class BBoxArgument extends StringArgument implements ContainerFactoryPluginInter $form['round_coordinates'] = [ '#type' => 'checkbox', '#title' => $this->t('Round coordinates'), - '#default_value' => $this->options['title'], + '#default_value' => $this->options['round_coordinates'], '#description' => $this->t('Round coordinates to two decimal places. This can help in caching bounding box queries. For instance, "-0.542,51.344,-0.367,51.368" and "-0.541,51.343,-0.368,51.369" would use the same SQL query.'), ]; + $form['bbox_wrap'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Wrap the bounding box around 360 degrees.'), + '#description' => $this->t('Depending on the projection of the data it is possible that the bounding box wraps around the edges of the map. This requires extra conditions in the views query. By default wrapping is enabled. Turn it off if the provided coordinates never wrap.'), + '#default_value' => $this->options['bbox_wrap'], + ]; $form['point_logic'] = [ '#type' => 'checkbox', '#title' => $this->t('Apply point logic only'), @@ -205,17 +213,39 @@ class BBoxArgument extends StringArgument implements ContainerFactoryPluginInter * * This condition identifies the features to be included in the view * and is the WHERE clause that needs to be built. + * + * When bbox_wrap is enabled and the bounding box wraps around 360 degrees + * (left > right), the longitude condition needs to use OR instead of AND: + * (l >= bbl OR r <= bbr) instead of (l < bbr && bbl < r) */ $values = []; - $ph = $this->placeholder(); - $exp1 = $this->_format_db_table_column_specifier($g["left"]) . " < {$ph}"; - $values[$ph] = $bbox["right"]; + // Check if wrapping is enabled and if the bbox wraps around + $wrap = $this->options['bbox_wrap'] && $bbox['left'] > $bbox['right']; + + if ($wrap) { + // Wrapping case: longitude >= left OR longitude <= right + $ph1 = $this->placeholder(); + $ph2 = $this->placeholder(); + $exp_lng = "(" . $this->_format_db_table_column_specifier($g["left"]) . " >= {$ph1} OR " . + $this->_format_db_table_column_specifier($g["right"]) . " <= {$ph2})"; + $values[$ph1] = $bbox["left"]; + $values[$ph2] = $bbox["right"]; + } + else { + // Normal case: left < right AND left < longitude + $ph = $this->placeholder(); + $exp1 = $this->_format_db_table_column_specifier($g["left"]) . " < {$ph}"; + $values[$ph] = $bbox["right"]; - $ph = $this->placeholder(); - $exp2 = "{$ph} < " . $this->_format_db_table_column_specifier($g["right"]); - $values[$ph] = $bbox["left"]; + $ph = $this->placeholder(); + $exp2 = "{$ph} < " . $this->_format_db_table_column_specifier($g["right"]); + $values[$ph] = $bbox["left"]; + + $exp_lng = "{$exp1} AND {$exp2}"; + } + // Latitude conditions (same for both cases) $ph = $this->placeholder(); $exp3 = $this->_format_db_table_column_specifier($g["bottom"]) . " < {$ph}"; $values[$ph] = $bbox["top"]; @@ -224,7 +254,7 @@ class BBoxArgument extends StringArgument implements ContainerFactoryPluginInter $exp4 = "{$ph} < " . $this->_format_db_table_column_specifier($g["top"]); $values[$ph] = $bbox["bottom"]; - $exp = "{$exp1} AND {$exp2} AND {$exp3} AND {$exp4}"; + $exp = "{$exp_lng} AND {$exp3} AND {$exp4}"; $group = $this->query->setWhereGroup('AND'); $this->query->addWhereExpression($group, $exp, $values); } @@ -314,10 +344,26 @@ class BBoxArgument extends StringArgument implements ContainerFactoryPluginInter return; } + // Add latitude conditions (same for both wrapping and non-wrapping) $this->query->addWhere('bbox', $field_lat, $bbox['bottom'], '>='); $this->query->addWhere('bbox', $field_lat, $bbox['top'], '<='); - $this->query->addWhere('bbox', $field_lng, $bbox['left'], '>='); - $this->query->addWhere('bbox', $field_lng, $bbox['right'], '<='); + + // Check if wrapping is enabled and if the bbox wraps around 360 degrees + $wrap = $this->options['bbox_wrap'] && $bbox['left'] > $bbox['right']; + + if ($wrap) { + // Wrapping case: use OR condition for longitude + // longitude >= left OR longitude <= right + $or = $this->query->getConnection()->condition('OR'); + $or->condition($field_lng, $bbox['left'], '>='); + $or->condition($field_lng, $bbox['right'], '<='); + $this->query->addWhere('bbox', $or); + } + else { + // Normal case: longitude between left and right + $this->query->addWhere('bbox', $field_lng, $bbox['left'], '>='); + $this->query->addWhere('bbox', $field_lng, $bbox['right'], '<='); + } } /** @@ -340,6 +386,12 @@ class BBoxArgument extends StringArgument implements ContainerFactoryPluginInter $values['right'] = (float) $exploded_values[2]; $values['top'] = (float) $exploded_values[3]; + // Normalize longitude values to -180..180 range if bbox_wrap is enabled + if ($this->options['bbox_wrap']) { + $values['left'] = $this->normalizeLongitude($values['left']); + $values['right'] = $this->normalizeLongitude($values['right']); + } + if ($this->options['round_coordinates']) { $values['left'] -= 0.005; $values['bottom'] -= 0.005; @@ -357,4 +409,22 @@ class BBoxArgument extends StringArgument implements ContainerFactoryPluginInter return $values; } + /** + * Normalize longitude to -180..180 range using floating point modulo. + * + * @param float $lon + * The longitude value to normalize. + * + * @return float + * The normalized longitude value. + */ + protected function normalizeLongitude($lon) { + // Use fmod for floating-point precision + $normalized = fmod($lon + 180, 360); + if ($normalized < 0) { + $normalized += 360; + } + return $normalized - 180; + } + }