LatLongHandler   A
last analyzed

Complexity

Total Complexity 10

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 10
lcom 1
cbo 6
dl 0
loc 128
rs 10
c 2
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A getBaseTableName() 0 3 1
A completeTable() 0 8 1
A getSortFieldNames() 0 4 1
A getInsertValues() 0 17 2
A addMatchConditions() 0 16 3
A addInRangeConditions() 0 14 1
1
<?php
2
3
namespace Wikibase\QueryEngine\SQLStore\DVHandler;
4
5
use Ask\Language\Description\ValueDescription;
6
use DataValues\DataValue;
7
use DataValues\Geo\GlobeMath;
8
use DataValues\Geo\Values\LatLongValue;
9
use Doctrine\DBAL\Query\QueryBuilder;
10
use Doctrine\DBAL\Schema\Table;
11
use Doctrine\DBAL\Types\Type;
12
use InvalidArgumentException;
13
use Wikibase\QueryEngine\QueryNotSupportedException;
14
use Wikibase\QueryEngine\SQLStore\DataValueHandler;
15
16
/**
17
 * Represents the mapping between LatLongValue and
18
 * the corresponding table in the store.
19
 *
20
 * @since 0.1
21
 *
22
 * @licence GNU GPL v2+
23
 * @author Jeroen De Dauw < [email protected] >
24
 * @author Thiemo Kreuz
25
 */
26
class LatLongHandler extends DataValueHandler {
27
28
	/**
29
	 * Default to the Earth/Moon longitude range
30
	 */
31
	const MINIMUM_LONGITUDE = -180;
32
33
	/**
34
	 * Default to approximately a second (1/3600) for range searches. This is an arbitrary
35
	 * decision because coordinates like 12°34'56" with a precision of a second are very common.
36
	 */
37
	const EPSILON = 0.00028;
38
39
	/**
40
	 * @var GlobeMath
41
	 */
42
	private $math;
43
44
	public function __construct() {
45
		$this->math = new GlobeMath();
46
	}
47
48
	/**
49
	 * @see DataValueHandler::getBaseTableName
50
	 *
51
	 * @return string
52
	 */
53
	protected function getBaseTableName() {
54
		return 'latlong';
55
	}
56
57
	/**
58
	 * @see DataValueHandler::completeTable
59
	 *
60
	 * @param Table $table
61
	 */
62
	protected function completeTable( Table $table ) {
63
		$table->addColumn( 'value_lat', Type::FLOAT );
64
		$table->addColumn( 'value_lon', Type::FLOAT );
65
		$table->addColumn( 'hash',      Type::STRING, array( 'length' => 32 ) );
66
67
		// TODO: We still need to find out if combined indexes are better or not.
68
		$table->addIndex( array( 'value_lon', 'value_lat' ) );
69
	}
70
71
	/**
72
	 * @see DataValueHandler::getSortFieldNames
73
	 *
74
	 * @return string[]
75
	 */
76
	public function getSortFieldNames() {
77
		// Order by West-East first
78
		return array( 'value_lon', 'value_lat' );
79
	}
80
81
	/**
82
	 * @see DataValueHandler::getInsertValues
83
	 *
84
	 * @param DataValue $value
85
	 *
86
	 * @return array
87
	 * @throws InvalidArgumentException
88
	 */
89
	public function getInsertValues( DataValue $value ) {
90
		if ( $value instanceof LatLongValue ) {
91
			$normalized = $this->math->normalizeLatLong( $value, self::MINIMUM_LONGITUDE );
92
		} else {
93
			throw new InvalidArgumentException( 'Value is not a LatLongValue.' );
94
		}
95
96
		$values = array(
97
			'value_lat' => $normalized->getLatitude(),
98
			'value_lon' => $normalized->getLongitude(),
99
100
			// No special human-readable hash needed, everything required is in the other fields.
101
			'hash' => $this->getEqualityFieldValue( $value ),
102
		);
103
104
		return $values;
105
	}
106
107
	/**
108
	 * @see DataValueHandler::addMatchConditions
109
	 *
110
	 * @param QueryBuilder $builder
111
	 * @param ValueDescription $description
112
	 *
113
	 * @throws InvalidArgumentException
114
	 * @throws QueryNotSupportedException
115
	 */
116
	public function addMatchConditions( QueryBuilder $builder, ValueDescription $description ) {
117
		$value = $description->getValue();
118
119
		if ( $value instanceof LatLongValue ) {
120
			$epsilon = self::EPSILON;
121
			$value = $this->math->normalizeLatLong( $value, self::MINIMUM_LONGITUDE );
122
		} else {
123
			throw new InvalidArgumentException( 'Value is not a LatLongValue.' );
124
		}
125
126
		if ( $description->getComparator() === ValueDescription::COMP_EQUAL ) {
127
			$this->addInRangeConditions( $builder, $value, $epsilon );
128
		} else {
129
			parent::addMatchConditions( $builder, $description );
130
		}
131
	}
132
133
	/**
134
	 * @param QueryBuilder $builder
135
	 * @param LatLongValue $value
136
	 * @param float|int $epsilon
137
	 */
138
	private function addInRangeConditions( QueryBuilder $builder, LatLongValue $value, $epsilon ) {
139
		$lat = $value->getLatitude();
140
		$lon = $value->getLongitude();
141
142
		$builder->andWhere( 'value_lat >= :min_lat' );
143
		$builder->andWhere( 'value_lat <= :max_lat' );
144
		$builder->andWhere( 'value_lon >= :min_lon' );
145
		$builder->andWhere( 'value_lon <= :max_lon' );
146
147
		$builder->setParameter( ':min_lat', $lat - $epsilon );
148
		$builder->setParameter( ':max_lat', $lat + $epsilon );
149
		$builder->setParameter( ':min_lon', $lon - $epsilon );
150
		$builder->setParameter( ':max_lon', $lon + $epsilon );
151
	}
152
153
}
154