Completed
Push — master ( b5479f...9bd1df )
by Mark
02:19
created

convertRationaltoFloat()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 1
1
<?php
2
/*
3
 * Copyright (c) 2011-2017 Mark C. Prins <[email protected]>
4
 *
5
 * Permission to use, copy, modify, and distribute this software for any
6
 * purpose with or without fee is hereby granted, provided that the above
7
 * copyright notice and this permission notice appear in all copies.
8
 *
9
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
 */
17
if (!defined('DOKU_INC')) {
18
	die ();
19
}
20
21
/**
22
 * DokuWiki Plugin spatialhelper (index component).
23
 *
24
 * @license BSD license
25
 * @author Mark Prins
26
 */
27
class helper_plugin_spatialhelper_index extends DokuWiki_Plugin {
28
	/**
29
	 * directory for index files.
30
	 *
31
	 * @var string
32
	 */
33
	protected $idx_dir = '';
34
35
	/**
36
	 * spatial index, well lookup list/array so we can do spatial queries.
37
	 * entries should be: array("geohash" => {"id1","id3",})
38
	 *
39
	 * @var array
40
	 */
41
	protected $spatial_idx = array();
42
43
	/**
44
	 * handle to the geoPHP plugin.
45
	 */
46
	protected $geophp;
47
48
	/**
49
	 * Constructor, initialises the spatial index.
50
	 */
51
	public function __construct() {
52
		// parent::__construct ();
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
53 View Code Duplication
		if (!$geophp = &plugin_load('helper', 'geophp')) {
54
			$message = 'helper_plugin_spatialhelper_index::spatialhelper_index: geophp plugin is not available.';
55
			msg($message, - 1);
56
			return "";
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
57
		}
58
59
		global $conf;
60
		$this->idx_dir = $conf ['indexdir'];
61
		// test if there is a spatialindex, if not build one for the wiki
62
		if (!@file_exists($this->idx_dir . '/spatial.idx')) {
63
			// creates and stores the index
64
			$this->generateSpatialIndex();
65
		} else {
66
			$this->spatial_idx = unserialize(io_readFile($this->idx_dir . '/spatial.idx', false));
67
			dbglog($this->spatial_idx, 'done loading spatial index');
68
		}
69
	}
70
71
	/**
72
	 * Update the spatial index for the page.
73
	 *
74
	 * @param string $id
75
	 *        	the document ID
76
	 */
77
	public function updateSpatialIndex($id) {
78
		$geotags = p_get_metadata($id, 'geo');
79
		if (empty ($geotags)) {
80
			return false;
81
		}
82
		if (empty ($geotags ['lon']) || empty ($geotags ['lat'])) {
83
			return false;
84
		}
85
		dbglog($geotags, "Geo metadata found for page $id");
86
		$geometry = new Point($geotags ['lon'], $geotags ['lat']);
87
		$geohash = $geometry->out('geohash');
88
		dbglog('Update index for geohash: ' . $geohash);
89
		$succes = $this->_addToIndex($geohash, $id);
0 ignored issues
show
Unused Code introduced by
$succes is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
90
	}
91
92
	/**
93
	 * Looks up the geohash(es) for the document in the index.
94
	 *
95
	 * @param String $id
96
	 *        	document ID
97
	 * @param array $index
98
	 *        	spatial index
99
	 */
100
	public function findHashesForId($id, $index) {
101
		$hashes = array();
102
		foreach ($index as $hash => $docIds) {
103
			if (in_array($id, $docIds, false)) {
104
				$hashes [] = $hash;
105
			}
106
		}
107
		dbglog($hashes, "Found the following hashes for $id (should only be 1)");
108
		return $hashes;
109
	}
110
111
	/**
112
	 * Deletes the page from the index.
113
	 *
114
	 * @param String $id
115
	 *        	document ID
116
	 */
117
	public function deleteFromIndex($id) {
118
		// check the index for document
119
		$knownHashes = $this->findHashesForId($id, $this->spatial_idx);
120
		if (empty ($knownHashes)) {
121
			return;
122
		}
123
124
		// TODO shortcut, need to make sure there is only one element, if not the index is corrupt
125
		$knownHash = $knownHashes [0];
126
		$knownIds = $this->spatial_idx [$knownHash];
127
		$i = array_search($id, $knownIds);
128
		dbglog("removing: $knownIds[$i] from the index.");
129
		unset ($knownIds [$i]);
130
		$this->spatial_idx [$knownHash] = $knownIds;
131
		if (empty ($this->spatial_idx [$knownHash])) {
132
			// dbglog ( "removing key: $knownHash from the index." );
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
133
			unset ($this->spatial_idx [$knownHash]);
134
		}
135
		$succes = $this->_saveIndex();
0 ignored issues
show
Unused Code introduced by
$succes is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
136
	}
137
138
	/**
139
	 * Save spatial index.
140
	 */
141
	private function _saveIndex() {
142
		return io_saveFile($this->idx_dir . '/spatial.idx', serialize($this->spatial_idx));
143
	}
144
145
	/**
146
	 * (re-)Generates the spatial index by running through all the pages in the wiki.
147
	 *
148
	 * @todo add an option to erase the old index
149
	 */
150
	public function generateSpatialIndex() {
151
		global $conf;
152
		require_once (DOKU_INC . 'inc/search.php');
153
		$pages = array();
154
		search($pages, $conf ['datadir'], 'search_allpages', array());
155
		foreach ($pages as $page) {
156
			$this->updateSpatialIndex($page ['id']);
157
		}
158
		// media
159
		$media = array();
160
		search($media, $conf ['mediadir'], 'search_media', array());
161
		foreach ($media as $medium) {
162
			if ($medium ['isimg']) {
163
				$this->indexImage($medium);
164
			}
165
		}
166
		return true;
167
	}
168
169
	/**
170
	 * Add an index entry for this file having EXIF / IPTC data.
171
	 *
172
	 * @param unknown_type $img Dokuwiki image
173
	 * @see http://www.php.net/manual/en/function.iptcparse.php
174
	 * @see http://php.net/manual/en/function.exif-read-data.php
175
	 *
176
	 * @return boolean true when image was succesfully added to the index.
177
	 */
178
	public function indexImage($img) {
179
		// test for supported files (jpeg only)
180
		if ((substr($img ['file'], - strlen('.jpg')) !== '.jpg') and (substr($img ['file'], - strlen('.jpeg')) !== '.jpeg')) {
181
			return false;
182
		}
183
184
		$geometry = $this->getCoordsFromExif($img ['id']);
185
		if (!$geometry) {
186
			return false;
187
		}
188
		$geohash = $geometry->out('geohash');
189
		// TODO truncate the geohash to something reasonable, otherwise they are
190
		// useless as an indexing mechanism eg. u1h73weckdrmskdqec3c9 is far too
191
		// precise, limit at ~9 as most GPS are not submeter accurate
192
		return $this->_addToIndex($geohash, 'media__' . $img ['id']);
193
	}
194
195
	/**
196
	 * retrieve GPS decimal coordinates from exif.
197
	 *
198
	 * @param
199
	 *        	$id
200
	 * @return Point or false
201
	 */
202
	public function getCoordsFromExif($id) {
203
		$exif = exif_read_data(mediaFN($id), 0, true);
204
		if (empty ($exif ['GPS'])) {
205
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by helper_plugin_spatialhel...ndex::getCoordsFromExif of type Point.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
206
		}
207
208
		$lat = $this->convertDMStoD(array(
209
				$exif ['GPS'] ['GPSLatitude'] [0],
210
				$exif ['GPS'] ['GPSLatitude'] [1],
211
				$exif ['GPS'] ['GPSLatitude'] [2],
212
				$exif ['GPS'] ['GPSLatitudeRef']
213
		));
214
215
		$lon = $this->convertDMStoD(array(
216
				$exif ['GPS'] ['GPSLongitude'] [0],
217
				$exif ['GPS'] ['GPSLongitude'] [1],
218
				$exif ['GPS'] ['GPSLongitude'] [2],
219
				$exif ['GPS'] ['GPSLongitudeRef']
220
		));
221
222
		return new Point($lon, $lat);
223
	}
224
225
	/**
226
	 * Store the hash/id entry in the index.
227
	 *
228
	 * @param string $geohash
229
	 * @param string $id
230
	 *        	page or media id
231
	 * @return boolean true when succesful
232
	 */
233
	private function _addToIndex($geohash, $id) {
234
		$pageIds = array();
235
		// check index for key/geohash
236
		if (!array_key_exists($geohash, $this->spatial_idx)) {
237
			dbglog("Geohash $geohash not in index, just add $id.");
238
			$pageIds [] = $id;
239
		} else {
240
			dbglog('Geohash for document is in index, find it.');
241
			// check the index for document
242
			$knownHashes = $this->findHashesForId($id, $this->spatial_idx);
243
			if (empty ($knownHashes)) {
244
				dbglog("No index record found for document $id, just add");
245
				$pageIds = $this->spatial_idx [$geohash];
246
				$pageIds [] = $id;
247
			}
248
			// TODO shortcut, need to make sure there is only one element, if not the index is corrupt
249
			$knownHash = $knownHashes [0];
250
251
			if ($knownHash == $geohash) {
252
				dbglog("Document $id was found in index and has the same geohash, nothing to do.");
253
				return true;
254
			}
255
256
			if (!empty ($knownHash)) {
257
				dbglog("Document/media $id was found in index but has different geohash (it moved).");
258
				$knownIds = $this->spatial_idx [$knownHash];
259
				dbglog($knownIds, "Known id's for this hash:");
260
				// remove it from the old geohash element
261
				$i = array_search($id, $knownIds);
262
				dbglog('Unsetting:' . $knownIds [$i]);
263
				unset ($knownIds [$i]);
264
				$this->spatial_idx [$knownHash] = $knownIds;
265
				// set on new geohash element
266
				$pageIds = $this->spatial_idx [$geohash];
267
				$pageIds [] = $id;
268
			}
269
		}
270
		// store and save
271
		$this->spatial_idx [$geohash] = $pageIds;
272
		return $this->_saveIndex();
273
	}
274
275
	/**
276
	 * convert DegreesMinutesSeconds to Decimal degrees.
277
	 *
278
	 * @param array $param array of rational DMS
279
	 * @return number
280
	 */
281
	public function convertDMStoD($param) {
282
		if (!is_array($param)) {
283
			$param = array($param);
284
		}
285
		$deg = $this->convertRationaltoFloat($param [0]);
286
		$min = $this->convertRationaltoFloat($param [1]) / 60;
287
		$sec = $this->convertRationaltoFloat($param [2]) / 60 / 60;
288
		// Hemisphere (N, S, W or E)
289
        $hem = ($param [3] === 'N' || $param [3] === 'E') ? 1 : -1;
290
        return $hem * ($deg + $min + $sec);
291
	}
292
293
	public function convertRationaltoFloat($param) {
294
		// rational64u
295
		$nums = explode('/', $param);
296
		if (intval($nums[1]) > 0) {
297
			return intval($nums[0]) / intval($nums[1]);
298
		} else {
299
			return intval($nums[0]);
300
		}
301
	}
302
}
303