...
...
@@ -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
+?>
...
...