| @@ -0,0 +1,967 @@ |
| @@ -0,0 +1,967 @@ |
| |
1
| +<?php |
| |
2
| + |
| |
3
| +# Copyright (c) 2010-2011 Arnaud Renevier, Inc, published under the modified BSD |
| |
4
| +# license. |
| |
5
| + |
| |
6
| +namespace gisconverter; |
| |
7
| + |
| |
8
| +abstract class CustomException extends \Exception { |
| |
9
| + protected $message; |
| |
10
| + public function __toString() { |
| |
11
| + return get_class($this) . " {$this->message} in {$this->file}({$this->line})\n{$this->getTraceAsString()}"; |
| |
12
| + } |
| |
13
| +} |
| |
14
| + |
| |
15
| +class Unimplemented extends CustomException { |
| |
16
| + public function __construct($message) { |
| |
17
| + $this->message = "unimplemented $message"; |
| |
18
| + } |
| |
19
| +} |
| |
20
| + |
| |
21
| +class UnimplementedMethod extends Unimplemented { |
| |
22
| + public function __construct($method, $class) { |
| |
23
| + $this->message = "method {$this->class}::{$this->method}"; |
| |
24
| + } |
| |
25
| +} |
| |
26
| + |
| |
27
| +class InvalidText extends CustomException { |
| |
28
| + public function __construct($decoder_name, $text = "") { |
| |
29
| + $this->message = "invalid text for decoder " . $decoder_name . ($text ? (": " . $text) : ""); |
| |
30
| + } |
| |
31
| +} |
| |
32
| + |
| |
33
| +class InvalidFeature extends CustomException { |
| |
34
| + public function __construct($decoder_name, $text = "") { |
| |
35
| + $this->message = "invalid feature for decoder $decoder_name" . ($text ? ": $text" : ""); |
| |
36
| + } |
| |
37
| +} |
| |
38
| + |
| |
39
| +abstract class OutOfRangeCoord extends CustomException { |
| |
40
| + private $coord; |
| |
41
| + public $type; |
| |
42
| + |
| |
43
| + public function __construct($coord) { |
| |
44
| + $this->message = "invalid {$this->type}: $coord"; |
| |
45
| + } |
| |
46
| +} |
| |
47
| +class OutOfRangeLon extends outOfRangeCoord { |
| |
48
| + public $type = "longitude"; |
| |
49
| +} |
| |
50
| +class OutOfRangeLat extends outOfRangeCoord { |
| |
51
| + public $type = "latitude"; |
| |
52
| +} |
| |
53
| + |
| |
54
| +class UnavailableResource extends CustomException { |
| |
55
| + public function __construct($ressource) { |
| |
56
| + $this->message = "unavailable ressource: $ressource"; |
| |
57
| + } |
| |
58
| +} |
| |
59
| + |
| |
60
| +interface iDecoder { |
| |
61
| + /* |
| |
62
| + * @param string $text |
| |
63
| + * @return Geometry |
| |
64
| + */ |
| |
65
| + static public function geomFromText($text); |
| |
66
| +} |
| |
67
| + |
| |
68
| +abstract class Decoder implements iDecoder { |
| |
69
| + static public function geomFromText($text) { |
| |
70
| + throw new UnimplementedMethod(__FUNCTION__, get_called_class()); |
| |
71
| + } |
| |
72
| +} |
| |
73
| + |
| |
74
| +interface iGeometry { |
| |
75
| + /* |
| |
76
| + * @return string |
| |
77
| + */ |
| |
78
| + public function toGeoJSON(); |
| |
79
| + |
| |
80
| + /* |
| |
81
| + * @return string |
| |
82
| + */ |
| |
83
| + public function toKML(); |
| |
84
| + |
| |
85
| + /* |
| |
86
| + * @return string |
| |
87
| + */ |
| |
88
| + public function toWKT(); |
| |
89
| + |
| |
90
| + /* |
| |
91
| + * @param mode: trkseg, rte or wpt |
| |
92
| + * @return string |
| |
93
| + */ |
| |
94
| + public function toGPX($mode = null); |
| |
95
| + |
| |
96
| + /* |
| |
97
| + * @param Geometry $geom |
| |
98
| + * @return boolean |
| |
99
| + */ |
| |
100
| + public function equals(Geometry $geom); |
| |
101
| +} |
| |
102
| + |
| |
103
| +abstract class Geometry implements iGeometry { |
| |
104
| + const name = ""; |
| |
105
| + |
| |
106
| + public function toGeoJSON() { |
| |
107
| + throw new UnimplementedMethod(__FUNCTION__, get_called_class()); |
| |
108
| + } |
| |
109
| + |
| |
110
| + public function toKML() { |
| |
111
| + throw new UnimplementedMethod(__FUNCTION__, get_called_class()); |
| |
112
| + } |
| |
113
| + |
| |
114
| + public function toGPX($mode = null) { |
| |
115
| + throw new UnimplementedMethod(__FUNCTION__, get_called_class()); |
| |
116
| + } |
| |
117
| + |
| |
118
| + public function toWKT() { |
| |
119
| + throw new UnimplementedMethod(__FUNCTION__, get_called_class()); |
| |
120
| + } |
| |
121
| + |
| |
122
| + public function equals(Geometry $geom) { |
| |
123
| + throw new UnimplementedMethod(__FUNCTION__, get_called_class()); |
| |
124
| + } |
| |
125
| + |
| |
126
| + public function __toString() { |
| |
127
| + return $this->toWKT(); |
| |
128
| + } |
| |
129
| +} |
| |
130
| + |
| |
131
| +class WKT extends Decoder { |
| |
132
| + static public function geomFromText($text) { |
| |
133
| + $ltext = strtolower($text); |
| |
134
| + $type_pattern = '/\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/'; |
| |
135
| + if (!preg_match($type_pattern, $ltext, $matches)) { |
| |
136
| + throw new InvalidText(__CLASS__, $text); |
| |
137
| + } |
| |
138
| + foreach (array("Point", "MultiPoint", "LineString", "MultiLinestring", "LinearRing", |
| |
139
| + "Polygon", "MultiPolygon", "GeometryCollection") as $wkt_type) { |
| |
140
| + if (strtolower($wkt_type) == $matches[1]) { |
| |
141
| + $type = $wkt_type; |
| |
142
| + break; |
| |
143
| + } |
| |
144
| + } |
| |
145
| + |
| |
146
| + if (!isset($type)) { |
| |
147
| + throw new InvalidText(__CLASS__, $text); |
| |
148
| + } |
| |
149
| + |
| |
150
| + try { |
| |
151
| + $components = call_user_func(array('static', 'parse' . $type), $matches[2]); |
| |
152
| + } catch(InvalidText $e) { |
| |
153
| + throw new InvalidText(__CLASS__, $text); |
| |
154
| + } catch(\Exception $e) { |
| |
155
| + throw $e; |
| |
156
| + } |
| |
157
| + |
| |
158
| + $constructor = __NAMESPACE__ . '\\' . $type; |
| |
159
| + return new $constructor($components); |
| |
160
| + } |
| |
161
| + |
| |
162
| + static protected function parsePoint($str) { |
| |
163
| + return preg_split('/\s+/', trim($str)); |
| |
164
| + } |
| |
165
| + |
| |
166
| + static protected function parseMultiPoint($str) { |
| |
167
| + $str = trim($str); |
| |
168
| + if (strlen ($str) == 0) { |
| |
169
| + return array(); |
| |
170
| + } |
| |
171
| + return static::parseLineString($str); |
| |
172
| + } |
| |
173
| + |
| |
174
| + static protected function parseLineString($str) { |
| |
175
| + $components = array(); |
| |
176
| + foreach (preg_split('/,/', trim($str)) as $compstr) { |
| |
177
| + $components[] = new Point(static::parsePoint($compstr)); |
| |
178
| + } |
| |
179
| + return $components; |
| |
180
| + } |
| |
181
| + |
| |
182
| + static protected function parseMultiLineString($str) { |
| |
183
| + return static::_parseCollection($str, "LineString"); |
| |
184
| + } |
| |
185
| + |
| |
186
| + static protected function parseLinearRing($str) { |
| |
187
| + return static::parseLineString($str); |
| |
188
| + } |
| |
189
| + |
| |
190
| + static protected function parsePolygon($str) { |
| |
191
| + return static::_parseCollection($str, "LinearRing"); |
| |
192
| + } |
| |
193
| + |
| |
194
| + static protected function parseMultiPolygon($str) { |
| |
195
| + return static::_parseCollection($str, "Polygon"); |
| |
196
| + } |
| |
197
| + |
| |
198
| + static protected function parseGeometryCollection($str) { |
| |
199
| + $components = array(); |
| |
200
| + foreach (preg_split('/,\s*(?=[A-Za-z])/', trim($str)) as $compstr) { |
| |
201
| + $components[] = static::geomFromText($compstr); |
| |
202
| + } |
| |
203
| + return $components; |
| |
204
| + } |
| |
205
| + |
| |
206
| + static protected function _parseCollection($str, $child_constructor) { |
| |
207
| + $components = array(); |
| |
208
| + foreach (preg_split('/\)\s*,\s*\(/', trim($str)) as $compstr) { |
| |
209
| + if (strlen($compstr) and $compstr[0] == '(') { |
| |
210
| + $compstr = substr($compstr, 1); |
| |
211
| + } |
| |
212
| + if (strlen($compstr) and $compstr[strlen($compstr)-1] == ')') { |
| |
213
| + $compstr = substr($compstr, 0, -1); |
| |
214
| + } |
| |
215
| + |
| |
216
| + $childs = call_user_func(array('static', 'parse' . $child_constructor), $compstr); |
| |
217
| + $constructor = __NAMESPACE__ . '\\' . $child_constructor; |
| |
218
| + $components[] = new $constructor($childs); |
| |
219
| + } |
| |
220
| + return $components; |
| |
221
| + } |
| |
222
| + |
| |
223
| +} |
| |
224
| + |
| |
225
| +class GeoJSON extends Decoder { |
| |
226
| + |
| |
227
| + static public function geomFromText($text) { |
| |
228
| + $ltext = strtolower($text); |
| |
229
| + $obj = json_decode($ltext); |
| |
230
| + if (is_null ($obj)) { |
| |
231
| + throw new InvalidText(__CLASS__, $text); |
| |
232
| + } |
| |
233
| + |
| |
234
| + try { |
| |
235
| + $geom = static::_geomFromJson($obj); |
| |
236
| + } catch(InvalidText $e) { |
| |
237
| + throw new InvalidText(__CLASS__, $text); |
| |
238
| + } catch(\Exception $e) { |
| |
239
| + throw $e; |
| |
240
| + } |
| |
241
| + |
| |
242
| + return $geom; |
| |
243
| + } |
| |
244
| + |
| |
245
| + static protected function _geomFromJson($json) { |
| |
246
| + if (property_exists ($json, "geometry") and is_object($json->geometry)) { |
| |
247
| + return static::_geomFromJson($json->geometry); |
| |
248
| + } |
| |
249
| + |
| |
250
| + if (!property_exists ($json, "type") or !is_string($json->type)) { |
| |
251
| + throw new InvalidText(__CLASS__); |
| |
252
| + } |
| |
253
| + |
| |
254
| + foreach (array("Point", "MultiPoint", "LineString", "MultiLinestring", "LinearRing", |
| |
255
| + "Polygon", "MultiPolygon", "GeometryCollection") as $json_type) { |
| |
256
| + if (strtolower($json_type) == $json->type) { |
| |
257
| + $type = $json_type; |
| |
258
| + break; |
| |
259
| + } |
| |
260
| + } |
| |
261
| + |
| |
262
| + if (!isset($type)) { |
| |
263
| + throw new InvalidText(__CLASS__); |
| |
264
| + } |
| |
265
| + |
| |
266
| + try { |
| |
267
| + $components = call_user_func(array('static', 'parse'.$type), $json); |
| |
268
| + } catch(InvalidText $e) { |
| |
269
| + throw new InvalidText(__CLASS__); |
| |
270
| + } catch(\Exception $e) { |
| |
271
| + throw $e; |
| |
272
| + } |
| |
273
| + |
| |
274
| + $constructor = __NAMESPACE__ . '\\' . $type; |
| |
275
| + return new $constructor($components); |
| |
276
| + } |
| |
277
| + |
| |
278
| + static protected function parsePoint($json) { |
| |
279
| + if (!property_exists ($json, "coordinates") or !is_array($json->coordinates)) { |
| |
280
| + throw new InvalidText(__CLASS__); |
| |
281
| + } |
| |
282
| + return $json->coordinates; |
| |
283
| + } |
| |
284
| + |
| |
285
| + static protected function parseMultiPoint($json) { |
| |
286
| + if (!property_exists ($json, "coordinates") or !is_array($json->coordinates)) { |
| |
287
| + throw new InvalidText(__CLASS__); |
| |
288
| + } |
| |
289
| + return array_map(function($coords) { |
| |
290
| + return new Point($coords); |
| |
291
| + }, $json->coordinates); |
| |
292
| + } |
| |
293
| + |
| |
294
| + static protected function parseLineString($json) { |
| |
295
| + return static::parseMultiPoint($json); |
| |
296
| + } |
| |
297
| + |
| |
298
| + static protected function parseMultiLineString($json) { |
| |
299
| + $components = array(); |
| |
300
| + if (!property_exists ($json, "coordinates") or !is_array($json->coordinates)) { |
| |
301
| + throw new InvalidText(__CLASS__); |
| |
302
| + } |
| |
303
| + foreach ($json->coordinates as $coordinates) { |
| |
304
| + $linecomp = array(); |
| |
305
| + foreach ($coordinates as $coordinates) { |
| |
306
| + $linecomp[] = new Point($coordinates); |
| |
307
| + } |
| |
308
| + $components[] = new LineString($linecomp); |
| |
309
| + } |
| |
310
| + return $components; |
| |
311
| + } |
| |
312
| + |
| |
313
| + static protected function parseLinearRing($json) { |
| |
314
| + return static::parseMultiPoint($json); |
| |
315
| + } |
| |
316
| + |
| |
317
| + static protected function parsePolygon($json) { |
| |
318
| + $components = array(); |
| |
319
| + if (!property_exists ($json, "coordinates") or !is_array($json->coordinates)) { |
| |
320
| + throw new InvalidText(__CLASS__); |
| |
321
| + } |
| |
322
| + foreach ($json->coordinates as $coordinates) { |
| |
323
| + $ringcomp = array(); |
| |
324
| + foreach ($coordinates as $coordinates) { |
| |
325
| + $ringcomp[] = new Point($coordinates); |
| |
326
| + } |
| |
327
| + $components[] = new LinearRing($ringcomp); |
| |
328
| + } |
| |
329
| + return $components; |
| |
330
| + } |
| |
331
| + |
| |
332
| + static protected function parseMultiPolygon($json) { |
| |
333
| + $components = array(); |
| |
334
| + if (!property_exists ($json, "coordinates") or !is_array($json->coordinates)) { |
| |
335
| + throw new InvalidText(__CLASS__); |
| |
336
| + } |
| |
337
| + foreach ($json->coordinates as $coordinates) { |
| |
338
| + $polycomp = array(); |
| |
339
| + foreach ($coordinates as $coordinates) { |
| |
340
| + $ringcomp = array(); |
| |
341
| + foreach ($coordinates as $coordinates) { |
| |
342
| + $ringcomp[] = new Point($coordinates); |
| |
343
| + } |
| |
344
| + $polycomp[] = new LinearRing($ringcomp); |
| |
345
| + } |
| |
346
| + $components[] = new Polygon($polycomp); |
| |
347
| + } |
| |
348
| + return $components; |
| |
349
| + } |
| |
350
| + |
| |
351
| + static protected function parseGeometryCollection($json) { |
| |
352
| + if (!property_exists ($json, "geometries") or !is_array($json->geometries)) { |
| |
353
| + throw new InvalidText(__CLASS__); |
| |
354
| + } |
| |
355
| + $components = array(); |
| |
356
| + foreach ($json->geometries as $geometry) { |
| |
357
| + $components[] = static::_geomFromJson($geometry); |
| |
358
| + } |
| |
359
| + |
| |
360
| + return $components; |
| |
361
| + } |
| |
362
| + |
| |
363
| +} |
| |
364
| + |
| |
365
| +abstract class XML extends Decoder { |
| |
366
| + static public function geomFromText($text) { |
| |
367
| + if (!function_exists("simplexml_load_string") || !function_exists("libxml_use_internal_errors")) { |
| |
368
| + throw new UnavailableResource("simpleXML"); |
| |
369
| + } |
| |
370
| + libxml_use_internal_errors(true); |
| |
371
| + $xmlobj = simplexml_load_string($text); |
| |
372
| + if ($xmlobj === false) { |
| |
373
| + throw new InvalidText(__CLASS__, $text); |
| |
374
| + } |
| |
375
| + |
| |
376
| + try { |
| |
377
| + $geom = static::_geomFromXML($xmlobj); |
| |
378
| + } catch(InvalidText $e) { |
| |
379
| + throw new InvalidText(__CLASS__, $text); |
| |
380
| + } catch(\Exception $e) { |
| |
381
| + throw $e; |
| |
382
| + } |
| |
383
| + |
| |
384
| + return $geom; |
| |
385
| + } |
| |
386
| + |
| |
387
| + static protected function childElements($xml, $nodename = "") { |
| |
388
| + $nodename = strtolower($nodename); |
| |
389
| + $res = array(); |
| |
390
| + foreach ($xml->children() as $child) { |
| |
391
| + if ($nodename) { |
| |
392
| + if (strtolower($child->getName()) == $nodename) { |
| |
393
| + array_push($res, $child); |
| |
394
| + } |
| |
395
| + } else { |
| |
396
| + array_push($res, $child); |
| |
397
| + } |
| |
398
| + } |
| |
399
| + return $res; |
| |
400
| + } |
| |
401
| + |
| |
402
| + static protected function _childsCollect($xml) { |
| |
403
| + $components = array(); |
| |
404
| + foreach (static::childElements($xml) as $child) { |
| |
405
| + try { |
| |
406
| + $geom = static::_geomFromXML($child); |
| |
407
| + $components[] = $geom; |
| |
408
| + } catch(InvalidText $e) { |
| |
409
| + } |
| |
410
| + } |
| |
411
| + |
| |
412
| + $ncomp = count($components); |
| |
413
| + if ($ncomp == 0) { |
| |
414
| + throw new InvalidText(__CLASS__); |
| |
415
| + } else if ($ncomp == 1) { |
| |
416
| + return $components[0]; |
| |
417
| + } else { |
| |
418
| + return new GeometryCollection($components); |
| |
419
| + } |
| |
420
| + } |
| |
421
| + |
| |
422
| + protected static function _geomFromXML($xml) {} |
| |
423
| +} |
| |
424
| + |
| |
425
| +class KML extends XML { |
| |
426
| + static protected function parsePoint($xml) { |
| |
427
| + $coordinates = static::_extractCoordinates($xml); |
| |
428
| + $coords = preg_split('/,/', (string)$coordinates[0]); |
| |
429
| + return array_map("trim", $coords); |
| |
430
| + } |
| |
431
| + |
| |
432
| + static protected function parseLineString($xml) { |
| |
433
| + $coordinates = static::_extractCoordinates($xml); |
| |
434
| + foreach (preg_split('/\s+/', trim((string)$coordinates[0])) as $compstr) { |
| |
435
| + $coords = preg_split('/,/', $compstr); |
| |
436
| + $components[] = new Point($coords); |
| |
437
| + } |
| |
438
| + return $components; |
| |
439
| + } |
| |
440
| + |
| |
441
| + static protected function parseLinearRing($xml) { |
| |
442
| + return static::parseLineString($xml); |
| |
443
| + } |
| |
444
| + |
| |
445
| + static protected function parsePolygon($xml) { |
| |
446
| + $ring = array(); |
| |
447
| + foreach (static::childElements($xml, 'outerboundaryis') as $elem) { |
| |
448
| + $ring = array_merge($ring, static::childElements($elem, 'linearring')); |
| |
449
| + } |
| |
450
| + |
| |
451
| + if (count($ring) != 1) { |
| |
452
| + throw new InvalidText(__CLASS__); |
| |
453
| + } |
| |
454
| + |
| |
455
| + $components = array(new LinearRing(static::parseLinearRing($ring[0]))); |
| |
456
| + foreach (static::childElements($xml, 'innerboundaryis') as $elem) { |
| |
457
| + foreach (static::childElements($elem, 'linearring') as $ring) { |
| |
458
| + $components[] = new LinearRing(static::parseLinearRing($ring[0])); |
| |
459
| + } |
| |
460
| + } |
| |
461
| + return $components; |
| |
462
| + } |
| |
463
| + |
| |
464
| + static protected function parseMultiGeometry($xml) { |
| |
465
| + $components = array(); |
| |
466
| + foreach ($xml->children() as $child) { |
| |
467
| + $components[] = static::_geomFromXML($child); |
| |
468
| + } |
| |
469
| + return $components; |
| |
470
| + } |
| |
471
| + |
| |
472
| + static protected function _extractCoordinates($xml) { |
| |
473
| + $coordinates = static::childElements($xml, 'coordinates'); |
| |
474
| + if (count($coordinates) != 1) { |
| |
475
| + throw new InvalidText(__CLASS__); |
| |
476
| + } |
| |
477
| + return $coordinates; |
| |
478
| + } |
| |
479
| + |
| |
480
| + static protected function _geomFromXML($xml) { |
| |
481
| + $nodename = strtolower($xml->getName()); |
| |
482
| + if ($nodename == "kml" or $nodename == "document" or $nodename == "placemark") { |
| |
483
| + return static::_childsCollect($xml); |
| |
484
| + } |
| |
485
| + |
| |
486
| + foreach (array("Point", "LineString", "LinearRing", "Polygon", "MultiGeometry") as $kml_type) { |
| |
487
| + if (strtolower($kml_type) == $nodename) { |
| |
488
| + $type = $kml_type; |
| |
489
| + break; |
| |
490
| + } |
| |
491
| + } |
| |
492
| + |
| |
493
| + if (!isset($type)) { |
| |
494
| + throw new InvalidText(__CLASS__); |
| |
495
| + } |
| |
496
| + |
| |
497
| + try { |
| |
498
| + $components = call_user_func(array('static', 'parse'.$type), $xml); |
| |
499
| + } catch(InvalidText $e) { |
| |
500
| + throw new InvalidText(__CLASS__); |
| |
501
| + } catch(\Exception $e) { |
| |
502
| + throw $e; |
| |
503
| + } |
| |
504
| + |
| |
505
| + if ($type == "MultiGeometry") { |
| |
506
| + if (count($components)) { |
| |
507
| + $possibletype = $components[0]::name; |
| |
508
| + $sametype = true; |
| |
509
| + foreach (array_slice($components, 1) as $component) { |
| |
510
| + if ($component::name != $possibletype) { |
| |
511
| + $sametype = false; |
| |
512
| + break; |
| |
513
| + } |
| |
514
| + } |
| |
515
| + if ($sametype) { |
| |
516
| + switch ($possibletype) { |
| |
517
| + case "Point": |
| |
518
| + return new MultiPoint($components); |
| |
519
| + break; |
| |
520
| + case "LineString": |
| |
521
| + return new MultiLineString($components); |
| |
522
| + break; |
| |
523
| + case "Polygon": |
| |
524
| + return new MultiPolygon($components); |
| |
525
| + break; |
| |
526
| + default: |
| |
527
| + break; |
| |
528
| + } |
| |
529
| + } |
| |
530
| + } |
| |
531
| + return new GeometryCollection($components); |
| |
532
| + } |
| |
533
| + |
| |
534
| + $constructor = __NAMESPACE__ . '\\' . $type; |
| |
535
| + return new $constructor($components); |
| |
536
| + } |
| |
537
| +} |
| |
538
| + |
| |
539
| +class GPX extends XML { |
| |
540
| + static protected function _extractCoordinates($xml) { |
| |
541
| + $attributes = $xml->attributes(); |
| |
542
| + $lon = (string) $attributes['lon']; |
| |
543
| + $lat = (string) $attributes['lat']; |
| |
544
| + if (!$lon or !$lat) { |
| |
545
| + throw new InvalidText(__CLASS__); |
| |
546
| + } |
| |
547
| + return array($lon, $lat); |
| |
548
| + } |
| |
549
| + |
| |
550
| + static protected function parseTrkseg($xml) { |
| |
551
| + $res = array(); |
| |
552
| + foreach ($xml->children() as $elem) { |
| |
553
| + if (strtolower($elem->getName()) == "trkpt") { |
| |
554
| + $res[] = new Point(static::_extractCoordinates($elem)); |
| |
555
| + } |
| |
556
| + } |
| |
557
| + return $res; |
| |
558
| + } |
| |
559
| + |
| |
560
| + static protected function parseRte($xml) { |
| |
561
| + $res = array(); |
| |
562
| + foreach ($xml->children() as $elem) { |
| |
563
| + if (strtolower($elem->getName()) == "rtept") { |
| |
564
| + $res[] = new Point(static::_extractCoordinates($elem)); |
| |
565
| + } |
| |
566
| + } |
| |
567
| + return $res; |
| |
568
| + } |
| |
569
| + |
| |
570
| + static protected function parseWpt($xml) { |
| |
571
| + return static::_extractCoordinates($xml); |
| |
572
| + } |
| |
573
| + |
| |
574
| + static protected function _geomFromXML($xml) { |
| |
575
| + $nodename = strtolower($xml->getName()); |
| |
576
| + if ($nodename == "gpx" or $nodename == "trk") { |
| |
577
| + return static::_childsCollect($xml); |
| |
578
| + } |
| |
579
| + foreach (array("Trkseg", "Rte", "Wpt") as $kml_type) { |
| |
580
| + if (strtolower($kml_type) == $xml->getName()) { |
| |
581
| + $type = $kml_type; |
| |
582
| + break; |
| |
583
| + } |
| |
584
| + } |
| |
585
| + |
| |
586
| + if (!isset($type)) { |
| |
587
| + throw new InvalidText(__CLASS__); |
| |
588
| + } |
| |
589
| + |
| |
590
| + try { |
| |
591
| + $components = call_user_func(array('static', 'parse'.$type), $xml); |
| |
592
| + } catch(InvalidText $e) { |
| |
593
| + throw new InvalidText(__CLASS__); |
| |
594
| + } catch(\Exception $e) { |
| |
595
| + throw $e; |
| |
596
| + } |
| |
597
| + |
| |
598
| + if ($type == "Trkseg" or $type == "Rte") { |
| |
599
| + $constructor = __NAMESPACE__ . '\\' . 'LineString'; |
| |
600
| + } else if ($type == "Wpt") { |
| |
601
| + $constructor = __NAMESPACE__ . '\\' . 'Point'; |
| |
602
| + } |
| |
603
| + return new $constructor($components); |
| |
604
| + } |
| |
605
| +} |
| |
606
| + |
| |
607
| +class Point extends Geometry { |
| |
608
| + const name = "Point"; |
| |
609
| + |
| |
610
| + private $lon; |
| |
611
| + private $lat; |
| |
612
| + |
| |
613
| + public function __construct($coords) { |
| |
614
| + if (count ($coords) < 2) { |
| |
615
| + throw new InvalidFeature(__CLASS__, "Point must have two coordinates"); |
| |
616
| + } |
| |
617
| + $lon = $coords[0]; |
| |
618
| + $lat = $coords[1]; |
| |
619
| + if (!$this->checkLon($lon)) { |
| |
620
| + throw new OutOfRangeLon($lon); |
| |
621
| + } |
| |
622
| + if (!$this->checkLat($lat)) { |
| |
623
| + throw new OutOfRangeLat($lat); |
| |
624
| + } |
| |
625
| + $this->lon = (float)$lon; |
| |
626
| + $this->lat = (float)$lat; |
| |
627
| + } |
| |
628
| + |
| |
629
| + public function __get($property) { |
| |
630
| + if ($property == "lon") { |
| |
631
| + return $this->lon; |
| |
632
| + } else if ($property == "lat") { |
| |
633
| + return $this->lat; |
| |
634
| + } else { |
| |
635
| + throw new \Exception ("Undefined property"); |
| |
636
| + } |
| |
637
| + } |
| |
638
| + |
| |
639
| + public function toWKT() { |
| |
640
| + return strtoupper(static::name) . "({$this->lon} {$this->lat})"; |
| |
641
| + } |
| |
642
| + |
| |
643
| + public function toKML() { |
| |
644
| + return "<" . static::name . "><coordinates>{$this->lon},{$this->lat}</coordinates></" . static::name . ">"; |
| |
645
| + } |
| |
646
| + |
| |
647
| + public function toGPX($mode = null) { |
| |
648
| + if (!$mode) { |
| |
649
| + $mode = "wpt"; |
| |
650
| + } |
| |
651
| + if ($mode != "wpt") { |
| |
652
| + throw new UnimplementedMethod(__FUNCTION__, get_called_class()); |
| |
653
| + } |
| |
654
| + return "<wpt lon=\"{$this->lon}\" lat=\"{$this->lat}\"></wpt>"; |
| |
655
| + } |
| |
656
| + |
| |
657
| + public function toGeoJSON() { |
| |
658
| + $value = (object)array ('type' => static::name, 'coordinates' => array($this->lon, $this->lat)); |
| |
659
| + return json_encode($value); |
| |
660
| + } |
| |
661
| + |
| |
662
| + public function equals(Geometry $geom) { |
| |
663
| + if (get_class ($geom) != get_class($this)) { |
| |
664
| + return false; |
| |
665
| + } |
| |
666
| + return $geom->lat == $this->lat && $geom->lon == $this->lon; |
| |
667
| + } |
| |
668
| + |
| |
669
| + private function checkLon($lon) { |
| |
670
| + if (!is_numeric($lon)) { |
| |
671
| + return false; |
| |
672
| + } |
| |
673
| + if ($lon < -180 || $lon > 180) { |
| |
674
| + return false; |
| |
675
| + } |
| |
676
| + return true; |
| |
677
| + } |
| |
678
| + private function checkLat($lat) { |
| |
679
| + if (!is_numeric($lat)) { |
| |
680
| + return false; |
| |
681
| + } |
| |
682
| + if ($lat < -90 || $lat > 90) { |
| |
683
| + return false; |
| |
684
| + } |
| |
685
| + return true; |
| |
686
| + } |
| |
687
| +} |
| |
688
| + |
| |
689
| +abstract class Collection extends Geometry { |
| |
690
| + protected $components; |
| |
691
| + |
| |
692
| + public function __get($property) { |
| |
693
| + if ($property == "components") { |
| |
694
| + return $this->components; |
| |
695
| + } else { |
| |
696
| + throw new \Exception ("Undefined property"); |
| |
697
| + } |
| |
698
| + } |
| |
699
| + |
| |
700
| + public function toWKT() { |
| |
701
| + $recursiveWKT = function ($geom) use (&$recursiveWKT) { |
| |
702
| + if ($geom instanceof Point) { |
| |
703
| + return "{$geom->lon} {$geom->lat}"; |
| |
704
| + } else { |
| |
705
| + return "(" . implode (',', array_map($recursiveWKT, $geom->components)). ")"; |
| |
706
| + } |
| |
707
| + }; |
| |
708
| + return strtoupper(static::name) . call_user_func($recursiveWKT, $this); |
| |
709
| + } |
| |
710
| + |
| |
711
| + public function toGeoJSON() { |
| |
712
| + $recurviseJSON = function ($geom) use (&$recurviseJSON) { |
| |
713
| + if ($geom instanceof Point) { |
| |
714
| + return array($geom->lon, $geom->lat); |
| |
715
| + } else { |
| |
716
| + return array_map($recurviseJSON, $geom->components); |
| |
717
| + } |
| |
718
| + }; |
| |
719
| + $value = (object)array ('type' => static::name, 'coordinates' => call_user_func($recurviseJSON, $this)); |
| |
720
| + return json_encode($value); |
| |
721
| + } |
| |
722
| + |
| |
723
| + public function toKML() { |
| |
724
| + return '<MultiGeometry>' . implode("", array_map(function($comp) { return $comp->toKML(); }, $this->components)) . '</MultiGeometry>'; |
| |
725
| + } |
| |
726
| + |
| |
727
| +} |
| |
728
| + |
| |
729
| +class MultiPoint extends Collection { |
| |
730
| + const name = "MultiPoint"; |
| |
731
| + |
| |
732
| + public function __construct($components) { |
| |
733
| + foreach ($components as $comp) { |
| |
734
| + if (!($comp instanceof Point)) { |
| |
735
| + throw new InvalidFeature(__CLASS__, static::name . " can only contain Point elements"); |
| |
736
| + } |
| |
737
| + } |
| |
738
| + $this->components = $components; |
| |
739
| + } |
| |
740
| + |
| |
741
| + public function equals(Geometry $geom) { |
| |
742
| + if (get_class ($geom) != get_class($this)) { |
| |
743
| + return false; |
| |
744
| + } |
| |
745
| + if (count($this->components) != count($geom->components)) { |
| |
746
| + return false; |
| |
747
| + } |
| |
748
| + foreach (range(0, count($this->components) - 1) as $count) { |
| |
749
| + if (!$this->components[$count]->equals($geom->components[$count])) { |
| |
750
| + return false; |
| |
751
| + } |
| |
752
| + } |
| |
753
| + return true; |
| |
754
| + } |
| |
755
| + |
| |
756
| +} |
| |
757
| + |
| |
758
| +class LineString extends MultiPoint { |
| |
759
| + const name = "LineString"; |
| |
760
| + public function __construct($components) { |
| |
761
| + if (count ($components) < 2) { |
| |
762
| + throw new InvalidFeature(__CLASS__, "LineString must have at least 2 points"); |
| |
763
| + } |
| |
764
| + parent::__construct($components); |
| |
765
| + } |
| |
766
| + |
| |
767
| + public function toKML() { |
| |
768
| + return "<" . static::name . "><coordinates>" . implode(" ", array_map(function($comp) { |
| |
769
| + return "{$comp->lon},{$comp->lat}"; |
| |
770
| + }, $this->components)). "</coordinates></" . static::name . ">"; |
| |
771
| + } |
| |
772
| + |
| |
773
| + public function toGPX($mode = null) { |
| |
774
| + if (!$mode) { |
| |
775
| + $mode = "trkseg"; |
| |
776
| + } |
| |
777
| + if ($mode != "trkseg" and $mode != "rte") { |
| |
778
| + throw new UnimplementedMethod(__FUNCTION__, get_called_class()); |
| |
779
| + } |
| |
780
| + if ($mode == "trkseg") { |
| |
781
| + return '<trkseg>' . implode ("", array_map(function ($comp) { |
| |
782
| + return "<trkpt lon=\"{$comp->lon}\" lat=\"{$comp->lat}\"></trkpt>"; |
| |
783
| + }, $this->components)). "</trkseg>"; |
| |
784
| + } else { |
| |
785
| + return '<rte>' . implode ("", array_map(function ($comp) { |
| |
786
| + return "<rtept lon=\"{$comp->lon}\" lat=\"{$comp->lat}\"></rtept>"; |
| |
787
| + }, $this->components)). "</rte>"; |
| |
788
| + } |
| |
789
| + } |
| |
790
| + |
| |
791
| +} |
| |
792
| + |
| |
793
| +class MultiLineString extends Collection { |
| |
794
| + const name = "MultiLineString"; |
| |
795
| + |
| |
796
| + public function __construct($components) { |
| |
797
| + foreach ($components as $comp) { |
| |
798
| + if (!($comp instanceof LineString)) { |
| |
799
| + throw new InvalidFeature(__CLASS__, "MultiLineString can only contain LineString elements"); |
| |
800
| + } |
| |
801
| + } |
| |
802
| + $this->components = $components; |
| |
803
| + } |
| |
804
| + |
| |
805
| +} |
| |
806
| + |
| |
807
| +class LinearRing extends LineString { |
| |
808
| + const name = "LinearRing"; |
| |
809
| + public function __construct($components) { |
| |
810
| + $first = $components[0]; |
| |
811
| + $last = end($components); |
| |
812
| + if (!$first->equals($last)) { |
| |
813
| + throw new InvalidFeature(__CLASS__, "LinearRing must be closed"); |
| |
814
| + } |
| |
815
| + parent::__construct($components); |
| |
816
| + } |
| |
817
| + public function contains(Geometry $geom) { |
| |
818
| + if ($geom instanceof Collection) { |
| |
819
| + foreach ($geom->components as $point) { |
| |
820
| + if (!$this->contains($point)) { |
| |
821
| + return false; |
| |
822
| + } |
| |
823
| + } |
| |
824
| + return true; |
| |
825
| + } else if ($geom instanceof Point) { |
| |
826
| + return $this->containsPoint($geom); |
| |
827
| + } else { |
| |
828
| + throw new Unimplemented(get_class($this) . "::" . __FUNCTION__ . " for " . get_class($geom) . " geometry"); |
| |
829
| + } |
| |
830
| + } |
| |
831
| + |
| |
832
| + protected function containsPoint(Point $point) { |
| |
833
| + /* |
| |
834
| + *PHP implementation of OpenLayers.Geometry.LinearRing.ContainsPoint algorithm |
| |
835
| + */ |
| |
836
| + $px = round($point->lon, 14); |
| |
837
| + $py = round($point->lat, 14); |
| |
838
| + |
| |
839
| + $crosses = 0; |
| |
840
| + foreach (range(0, count($this->components) - 2) as $i) { |
| |
841
| + $start = $this->components[$i]; |
| |
842
| + $x1 = round($start->lon, 14); |
| |
843
| + $y1 = round($start->lat, 14); |
| |
844
| + $end = $this->components[$i + 1]; |
| |
845
| + $x2 = round($end->lon, 14); |
| |
846
| + $y2 = round($end->lat, 14); |
| |
847
| + |
| |
848
| + if($y1 == $y2) { |
| |
849
| + // horizontal edge |
| |
850
| + if($py == $y1) { |
| |
851
| + // point on horizontal line |
| |
852
| + if($x1 <= $x2 && ($px >= $x1 && $px <= $x2) || // right or vert |
| |
853
| + $x1 >= $x2 && ($px <= $x1 && $px >= $x2)) { // left or vert |
| |
854
| + // point on edge |
| |
855
| + $crosses = -1; |
| |
856
| + break; |
| |
857
| + } |
| |
858
| + } |
| |
859
| + // ignore other horizontal edges |
| |
860
| + continue; |
| |
861
| + } |
| |
862
| + |
| |
863
| + $cx = round(((($x1 - $x2) * $py) + (($x2 * $y1) - ($x1 * $y2))) / ($y1 - $y2), 14); |
| |
864
| + |
| |
865
| + if($cx == $px) { |
| |
866
| + // point on line |
| |
867
| + if($y1 < $y2 && ($py >= $y1 && $py <= $y2) || // upward |
| |
868
| + $y1 > $y2 && ($py <= $y1 && $py >= $y2)) { // downward |
| |
869
| + // point on edge |
| |
870
| + $crosses = -1; |
| |
871
| + break; |
| |
872
| + } |
| |
873
| + } |
| |
874
| + if($cx <= $px) { |
| |
875
| + // no crossing to the right |
| |
876
| + continue; |
| |
877
| + } |
| |
878
| + if($x1 != $x2 && ($cx < min($x1, $x2) || $cx > max($x1, $x2))) { |
| |
879
| + // no crossing |
| |
880
| + continue; |
| |
881
| + } |
| |
882
| + if($y1 < $y2 && ($py >= $y1 && $py < $y2) || // upward |
| |
883
| + $y1 > $y2 && ($py < $y1 && $py >= $y2)) { // downward |
| |
884
| + $crosses++; |
| |
885
| + } |
| |
886
| + } |
| |
887
| + $contained = ($crosses == -1) ? |
| |
888
| + // on edge |
| |
889
| + 1 : |
| |
890
| + // even (out) or odd (in) |
| |
891
| + !!($crosses & 1); |
| |
892
| + |
| |
893
| + return $contained; |
| |
894
| + } |
| |
895
| + |
| |
896
| +} |
| |
897
| + |
| |
898
| +class Polygon extends Collection { |
| |
899
| + const name = "Polygon"; |
| |
900
| + public function __construct($components) { |
| |
901
| + $outer = $components[0]; |
| |
902
| + foreach (array_slice($components, 1) as $inner) { |
| |
903
| + if (!$outer->contains($inner)) { |
| |
904
| + throw new InvalidFeature(__CLASS__, "Polygon inner rings must be enclosed in outer ring"); |
| |
905
| + } |
| |
906
| + } |
| |
907
| + foreach ($components as $comp) { |
| |
908
| + if (!($comp instanceof LinearRing)) { |
| |
909
| + throw new InvalidFeature(__CLASS__, "Polygon can only contain LinearRing elements"); |
| |
910
| + } |
| |
911
| + } |
| |
912
| + $this->components = $components; |
| |
913
| + } |
| |
914
| + |
| |
915
| + public function toKML() { |
| |
916
| + $str = '<outerBoundaryIs>' . $this->components[0]->toKML() . '</outerBoundaryIs>'; |
| |
917
| + $str .= implode("", array_map(function($comp) { |
| |
918
| + return '<innerBoundaryIs>' . $comp->toKML() . '</innerBoundaryIs>'; |
| |
919
| + }, array_slice($this->components, 1))); |
| |
920
| + return '<' . static::name . '>' . $str . '</' . static::name . '>'; |
| |
921
| + } |
| |
922
| + |
| |
923
| +} |
| |
924
| + |
| |
925
| +class MultiPolygon extends Collection { |
| |
926
| + const name = "MultiPolygon"; |
| |
927
| + |
| |
928
| + public function __construct($components) { |
| |
929
| + foreach ($components as $comp) { |
| |
930
| + if (!($comp instanceof Polygon)) { |
| |
931
| + throw new InvalidFeature(__CLASS__, "MultiPolygon can only contain Polygon elements"); |
| |
932
| + } |
| |
933
| + } |
| |
934
| + $this->components = $components; |
| |
935
| + } |
| |
936
| +} |
| |
937
| + |
| |
938
| +class GeometryCollection extends Collection { |
| |
939
| + const name = "GeometryCollection"; |
| |
940
| + |
| |
941
| + public function __construct($components) { |
| |
942
| + foreach ($components as $comp) { |
| |
943
| + if (!($comp instanceof Geometry)) { |
| |
944
| + throw new InvalidFeature(__CLASS__, "GeometryCollection can only contain Geometry elements"); |
| |
945
| + } |
| |
946
| + } |
| |
947
| + $this->components = $components; |
| |
948
| + } |
| |
949
| + |
| |
950
| + public function toWKT() { |
| |
951
| + return strtoupper(static::name) . "(" . implode(',', array_map(function ($comp) { |
| |
952
| + return $comp->toWKT(); |
| |
953
| + }, $this->components)) . ')'; |
| |
954
| + } |
| |
955
| + |
| |
956
| + public function toGeoJSON() { |
| |
957
| + $value = (object)array ('type' => static::name, 'geometries' => |
| |
958
| + array_map(function ($comp) { |
| |
959
| + // XXX: quite ugly |
| |
960
| + return json_decode($comp->toGeoJSON()); |
| |
961
| + }, $this->components) |
| |
962
| + ); |
| |
963
| + return json_encode($value); |
| |
964
| + } |
| |
965
| +} |
| |
966
| + |
| |
967
| +?> |