Subversion Repositories eFlore/Projets.eflore-projets

Rev

Rev 1001 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1001 delphine 1
<?php
2
/**
3
 * Author : Julien Moquet
4
 *
5
 * Inspired by Proj4js from Mike Adair madairATdmsolutions.ca
6
 *                      and Richard Greenwood rich@greenwoodmap.com
7
 * License: LGPL as per: http://www.gnu.org/copyleft/lesser.html
8
 */
9
class Proj4phpProj {
10
 
11
    /**
12
     * Property: readyToUse
13
     * Flag to indicate if initialization is complete for $this Proj object
14
     */
15
    public $readyToUse = false;
16
 
17
    /**
18
     * Property: title
19
     * The title to describe the projection
20
     */
21
    public $title = null;
22
 
23
    /**
24
     * Property: projName
25
     * The projection class for $this projection, e.g. lcc (lambert conformal conic,
26
     * or merc for mercator).  These are exactly equivalent to their Proj4
27
     * counterparts.
28
     */
29
    public $projName = null;
30
 
31
    /**
32
     * Property: projection
33
     * The projection object for $this projection. */
34
    public $projection = null;
35
 
36
    /**
37
     * Property: units
38
     * The units of the projection.  Values include 'm' and 'degrees'
39
     */
40
    public $units = null;
41
 
42
    /**
43
     * Property: datum
44
     * The datum specified for the projection
45
     */
46
    public $datum = null;
47
 
48
    /**
49
     * Property: x0
50
     * The x coordinate origin
51
     */
52
    public $x0 = 0;
53
 
54
    /**
55
     * Property: y0
56
     * The y coordinate origin
57
     */
58
    public $y0 = 0;
59
 
60
    /**
61
     * Property: localCS
62
     * Flag to indicate if the projection is a local one in which no transforms
63
     * are required.
64
     */
65
    public $localCS = false;
66
 
67
    /**
68
     *
69
     * @var type
70
     */
71
    protected $wktRE = '/^(\w+)\[(.*)\]$/';
72
 
73
    /**
74
     * Constructor: initialize
75
     * Constructor for Proj4php::Proj objects
76
     *
77
     * Parameters:
78
     * $srsCode - a code for map projection definition parameters.  These are usually
79
     * (but not always) EPSG codes.
80
     */
81
    public function __construct( $srsCode ) {
82
 
83
        $this->srsCodeInput = $srsCode;
84
 
85
        //check to see if $this is a WKT string
86
        if( (strpos( $srsCode, 'GEOGCS' ) !== false) ||
87
            (strpos( $srsCode, 'GEOCCS' ) !== false) ||
88
            (strpos( $srsCode, 'PROJCS' ) !== false) ||
89
            (strpos( $srsCode, 'LOCAL_CS' ) !== false) ) {
90
            $this->parseWKT( $srsCode );
91
            $this->deriveConstants();
92
            $this->loadProjCode( $this->projName );
93
            return;
94
        }
95
 
96
        // DGR 2008-08-03 : support urn and url
97
        if( strpos( $srsCode, 'urn:' ) === 0 ) {
98
            //urn:ORIGINATOR:def:crs:CODESPACE:VERSION:ID
99
            $urn = explode( ':', $srsCode );
100
            if( ($urn[1] == 'ogc' || $urn[1] == 'x-ogc') &&
101
                ($urn[2] == 'def') &&
102
                ($urn[3] == 'crs') ) {
103
                $srsCode = $urn[4] . ':' . $urn[strlen( $urn ) - 1];
104
            }
105
        } else if( strpos( $srsCode, 'http://' ) === 0 ) {
106
            //url#ID
107
            $url = explode( '#', $srsCode );
108
            if( preg_match( "/epsg.org/", $url[0] ) ) {
109
                // http://www.epsg.org/#
110
                $srsCode = 'EPSG:' . $url[1];
111
            } else if( preg_match( "/RIG.xml/", $url[0] ) ) {
112
                //http://librairies.ign.fr/geoportail/resources/RIG.xml#
113
                //http://interop.ign.fr/registers/ign/RIG.xml#
114
                $srsCode = 'IGNF:' . $url[1];
115
            }
116
        }
117
        $this->srsCode = strtoupper( $srsCode );
118
        if( strpos( $this->srsCode, "EPSG" ) === 0 ) {
119
            $this->srsCode = $this->srsCode;
120
            $this->srsAuth = 'epsg';
121
            $this->srsProjNumber = substr( $this->srsCode, 5 );
122
            // DGR 2007-11-20 : authority IGNF
123
        } else if( strpos( $this->srsCode, "IGNF" ) === 0 ) {
124
            $this->srsCode = $this->srsCode;
125
            $this->srsAuth = 'IGNF';
126
            $this->srsProjNumber = substr( $this->srsCode, 5 );
127
            // DGR 2008-06-19 : pseudo-authority CRS for WMS
128
        } else if( strpos( $this->srsCode, "CRS" ) === 0 ) {
129
            $this->srsCode = $this->srsCode;
130
            $this->srsAuth = 'CRS';
131
            $this->srsProjNumber = substr( $this->srsCode, 4 );
132
        } else {
133
            $this->srsAuth = '';
134
            $this->srsProjNumber = $this->srsCode;
135
        }
136
        $this->loadProjDefinition();
137
    }
138
 
139
    /**
140
     * Function: loadProjDefinition
141
     *    Loads the coordinate system initialization string if required.
142
     *    Note that dynamic loading happens asynchronously so an application must
143
     *    wait for the readyToUse property is set to true.
144
     *    To prevent dynamic loading, include the defs through a script tag in
145
     *    your application.
146
     *
147
     */
148
    public function loadProjDefinition() {
149
 
150
        //check in memory
151
        if( array_key_exists( $this->srsCode, Proj4php::$defs ) ) {
152
            $this->defsLoaded();
153
            return;
154
        }
155
        //else check for def on the server
156
        $filename = dirname( __FILE__ ) . '/defs/' . strtoupper( $this->srsAuth ) . $this->srsProjNumber . '.php';
157
 
158
        try {
159
            Proj4php::loadScript( $filename );
160
            $this->defsLoaded(); // succes
161
 
162
        } catch ( Exception $e ) {
163
            $this->loadFromService(); // fail
164
        }
165
    }
166
 
167
    /**
168
     * Function: loadFromService
169
     *    Creates the REST URL for loading the definition from a web service and
170
     *    loads it.
171
     *
172
     *
173
     * DO IT AGAIN. : SHOULD PHP CODE BE GET BY WEBSERVICES ?
174
     */
175
    public function loadFromService() {
176
 
177
        //else load from web service
178
        $url = Proj4php::$defsLookupService . '/' . $this->srsAuth . '/' . $this->srsProjNumber . '/proj4/';
179
        try {
180
            Proj4php::$defs[strtoupper($this->srsAuth) . ":" . $this->srsProjNumber] = Proj4php::loadScript( $url );
181
        } catch ( Exception $e ) {
182
            $this->defsFailed();
183
        }
184
    }
185
 
186
    /**
187
     * Function: defsLoaded
188
     * Continues the Proj object initilization once the def file is loaded
189
     *
190
     */
191
    public function defsLoaded() {
192
 
193
        $this->parseDefs();
194
        $this->loadProjCode( $this->projName );
195
    }
196
 
197
    /**
198
     * Function: checkDefsLoaded
199
     *    $this is the loadCheck method to see if the def object exists
200
     *
201
     */
202
    public function checkDefsLoaded() {
203
        return isset(Proj4php::$defs[$this->srsCode]) && !empty(Proj4php::$defs[$this->srsCode]);
204
    }
205
 
206
    /**
207
     * Function: defsFailed
208
     *    Report an error in loading the defs file, but continue on using WGS84
209
     *
210
     */
211
    public function defsFailed() {
212
        Proj4php::reportError( 'failed to load projection definition for: ' . $this->srsCode );
213
        Proj4php::$defs[$this->srsCode] = Proj4php::$defs['WGS84'];  //set it to something so it can at least continue
214
        $this->defsLoaded();
215
    }
216
 
217
    /**
218
     * Function: loadProjCode
219
     *    Loads projection class code dynamically if required.
220
     *     Projection code may be included either through a script tag or in
221
     *     a built version of proj4php
222
     *
223
     * An exception occurs if the projection is not found.
224
     */
225
    public function loadProjCode( $projName ) {
226
 
227
        if( array_key_exists( $projName, Proj4php::$proj ) ) {
228
            $this->initTransforms();
229
            return;
230
        }
231
        //the filename for the projection code
232
        $filename = dirname( __FILE__ ) . '/projCode/' . $projName . '.php';
233
 
234
        try {
235
            Proj4php::loadScript( $filename );
236
            $this->loadProjCodeSuccess( $projName );
237
 
238
        } catch ( Exception $e ) {
239
            $this->loadProjCodeFailure( $projName );
240
        }
241
    }
242
 
243
    /**
244
     * Function: loadProjCodeSuccess
245
     *    Loads any proj dependencies or continue on to final initialization.
246
     *
247
     */
248
    public function loadProjCodeSuccess( $projName ) {
249
 
250
        if( isset(Proj4php::$proj[$projName]->dependsOn) && !empty(Proj4php::$proj[$projName]->dependsOn)) {
251
            $this->loadProjCode( Proj4php::$proj[$projName]->dependsOn );
252
        } else {
253
            $this->initTransforms();
254
        }
255
    }
256
 
257
    /**
258
     * Function: defsFailed
259
     *    Report an error in loading the proj file.  Initialization of the Proj
260
     *    object has failed and the readyToUse flag will never be set.
261
     *
262
     */
263
    public function loadProjCodeFailure( $projName ) {
264
        Proj4php::reportError( "failed to find projection file for: " . $projName );
265
        //TBD initialize with identity transforms so proj will still work?
266
    }
267
 
268
    /**
269
     * Function: checkCodeLoaded
270
     *    $this is the loadCheck method to see if the projection code is loaded
271
     *
272
     */
273
    public function checkCodeLoaded( $projName ) {
274
 
275
        return isset(Proj4php::$proj[$projName]) && !empty(Proj4php::$proj[$projName]);
276
    }
277
 
278
    /**
279
     * Function: initTransforms
280
     *    Finalize the initialization of the Proj object
281
     *
282
     */
283
    public function initTransforms() {
284
 
285
        $this->projection = clone(Proj4php::$proj[$this->projName]);
286
        Proj4php::extend( $this->projection, $this );
287
        $this->init();
288
 
289
        // initiate depending class
290
        if( false !== ($dependsOn = isset($this->projection->dependsOn) && !empty($this->projection->dependsOn) ? $this->projection->dependsOn : false) ) {
291
            Proj4php::extend( Proj4php::$proj[$dependsOn], $this->projection) &&
292
            Proj4php::$proj[$dependsOn]->init() &&
293
            Proj4php::extend( $this->projection, Proj4php::$proj[$dependsOn] );
294
        }
295
 
296
        $this->readyToUse = true;
297
    }
298
 
299
    /**
300
     *
301
     */
302
    public function init() {
303
        $this->projection->init();
304
    }
305
 
306
    /**
307
     *
308
     * @param type $pt
309
     * @return type
310
     */
311
    public function forward( $pt ) {
312
        return $this->projection->forward( $pt );
313
    }
314
 
315
    /**
316
     *
317
     * @param type $pt
318
     * @return type
319
     */
320
    public function inverse( $pt ) {
321
        return $this->projection->inverse( $pt );
322
    }
323
 
324
    /**
325
     * Function: parseWKT
326
     * Parses a WKT string to get initialization parameters
327
     *
328
     */
329
    public function parseWKT( $wkt ) {
330
 
331
        if( false === ($match = preg_match( $this->wktRE, $wkt, $wktMatch )) )
332
            return;
333
 
334
        $wktObject = $wktMatch[1];
335
        $wktContent = $wktMatch[2];
336
        $wktTemp = explode( ",", $wktContent );
337
 
338
        $wktName = (strtoupper($wktObject) == "TOWGS84") ? "TOWGS84" : array_shift( $wktTemp );
339
        $wktName = preg_replace( '/^\"/', "", $wktName );
340
        $wktName = preg_replace( '/\"$/', "", $wktName );
341
 
342
        /*
343
          $wktContent = implode(",",$wktTemp);
344
          $wktArray = explode("],",$wktContent);
345
          for ($i=0; i<sizeof($wktArray)-1; ++$i) {
346
          $wktArray[$i] .= "]";
347
          }
348
         */
349
 
350
        $wktArray = array();
351
        $bkCount = 0;
352
        $obj = "";
353
 
354
        foreach( $wktTemp as $token ) {
355
 
356
            $bkCount = substr_count($token, "[") - substr_count($token, "]");
357
 
358
            // ???
359
            $obj .= $token;
360
            if( $bkCount === 0 ) {
361
                array_push( $wktArray, $obj );
362
                $obj = "";
363
            } else {
364
                $obj .= ",";
365
            }
366
        }
367
 
368
        //do something based on the type of the wktObject being parsed
369
        //add in variations in the spelling as required
370
        switch( $wktObject ) {
371
            case 'LOCAL_CS':
372
                $this->projName = 'identity';
373
                $this->localCS = true;
374
                $this->srsCode = $wktName;
375
                break;
376
            case 'GEOGCS':
377
                $this->projName = 'longlat';
378
                $this->geocsCode = $wktName;
379
                if( !$this->srsCode )
380
                    $this->srsCode = $wktName;
381
                break;
382
            case 'PROJCS':
383
                $$this->srsCode = $wktName;
384
                break;
385
            case 'GEOCCS':
386
                break;
387
            case 'PROJECTION':
388
                $this->projName = Proj4php::$wktProjections[$wktName];
389
                break;
390
            case 'DATUM':
391
                $this->datumName = $wktName;
392
                break;
393
            case 'LOCAL_DATUM':
394
                $this->datumCode = 'none';
395
                break;
396
            case 'SPHEROID':
397
                $this->ellps = $wktName;
398
                $this->a = floatval( array_shift( $wktArray ) );
399
                $this->rf = floatval( array_shift( $wktArray ) );
400
                break;
401
            case 'PRIMEM':
402
                $this->from_greenwich = floatval( array_shift( $wktArray ) ); //to radians?
403
                break;
404
            case 'UNIT':
405
                $this->units = $wktName;
406
                $this->unitsPerMeter = floatval( array_shift( $wktArray ) );
407
                break;
408
            case 'PARAMETER':
409
                $name = strtolower( $wktName );
410
                $value = floatval( array_shift( $wktArray ) );
411
                //there may be many variations on the wktName values, add in case
412
                //statements as required
413
                switch( $name ) {
414
                    case 'false_easting':
415
                        $this->x0 = $value;
416
                        break;
417
                    case 'false_northing':
418
                        $this->y0 = $value;
419
                        break;
420
                    case 'scale_factor':
421
                        $this->k0 = $value;
422
                        break;
423
                    case 'central_meridian':
424
                        $this->long0 = $value * Proj4php::$common->D2R;
425
                        break;
426
                    case 'latitude_of_origin':
427
                        $this->lat0 = $value * Proj4php::$common->D2R;
428
                        break;
429
                    case 'more_here':
430
                        break;
431
                    default:
432
                        break;
433
                }
434
                break;
435
            case 'TOWGS84':
436
                $this->datum_params = $wktArray;
437
                break;
438
            //DGR 2010-11-12: AXIS
439
            case 'AXIS':
440
                $name = strtolower( $wktName );
441
                $value = array_shift( $wktArray );
442
                switch( $value ) {
443
                    case 'EAST' : $value = 'e';
444
                        break;
445
                    case 'WEST' : $value = 'w';
446
                        break;
447
                    case 'NORTH': $value = 'n';
448
                        break;
449
                    case 'SOUTH': $value = 's';
450
                        break;
451
                    case 'UP' : $value = 'u';
452
                        break;
453
                    case 'DOWN' : $value = 'd';
454
                        break;
455
                    case 'OTHER':
456
                    default : $value = ' ';
457
                        break; //FIXME
458
                }
459
                if( !$this->axis ) {
460
                    $this->axis = "enu";
461
                }
462
                switch( $name ) {
463
                    case 'X': $this->axis = $value . substr( $this->axis, 1, 2 );
464
                        break;
465
                    case 'Y': $this->axis = substr( $this->axis, 0, 1 ) . $value . substr( $this->axis, 2, 1 );
466
                        break;
467
                    case 'Z': $this->axis = substr( $this->axis, 0, 2 ) . $value;
468
                        break;
469
                    default : break;
470
                }
471
            case 'MORE_HERE':
472
                break;
473
            default:
474
                break;
475
        }
476
 
477
        foreach( $wktArray as $wktArrayContent )
478
            $this->parseWKT( $wktArrayContent );
479
    }
480
 
481
    /**
482
     * Function: parseDefs
483
     * Parses the PROJ.4 initialization string and sets the associated properties.
484
     *
485
     */
486
    public function parseDefs() {
487
 
488
        $this->defData = Proj4php::$defs[$this->srsCode];
489
        #$paramName;
490
        #$paramVal;
491
        if( !$this->defData ) {
492
            return;
493
        }
494
        $paramArray = explode( "+", $this->defData );
495
        for( $prop = 0; $prop < sizeof( $paramArray ); $prop++ ) {
496
            if( strlen( $paramArray[$prop] ) == 0 )
497
                continue;
498
            $property = explode( "=", $paramArray[$prop] );
499
            $paramName = strtolower( $property[0] );
500
            if( sizeof( $property ) >= 2 ) {
501
                $paramVal = $property[1];
502
            }
503
 
504
            switch( trim( $paramName ) ) {  // trim out spaces
505
                case "": break;   // throw away nameless parameter
506
                case "title": $this->title = $paramVal;
507
                    break;
508
                case "proj": $this->projName = trim( $paramVal );
509
                    break;
510
                case "units": $this->units = trim( $paramVal );
511
                    break;
512
                case "datum": $this->datumCode = trim( $paramVal );
513
                    break;
514
                case "nadgrids": $this->nagrids = trim( $paramVal );
515
                    break;
516
                case "ellps": $this->ellps = trim( $paramVal );
517
                    break;
518
                case "a": $this->a = floatval( $paramVal );
519
                    break;  // semi-major radius
520
                case "b": $this->b = floatval( $paramVal );
521
                    break;  // semi-minor radius
522
                // DGR 2007-11-20
523
                case "rf": $this->rf = floatval( paramVal );
524
                    break; // inverse flattening rf= a/(a-b)
525
                case "lat_0": $this->lat0 = $paramVal * Proj4php::$common->D2R;
526
                    break;        // phi0, central latitude
527
                case "lat_1": $this->lat1 = $paramVal * Proj4php::$common->D2R;
528
                    break;        //standard parallel 1
529
                case "lat_2": $this->lat2 = $paramVal * Proj4php::$common->D2R;
530
                    break;        //standard parallel 2
531
                case "lat_ts": $this->lat_ts = $paramVal * Proj4php::$common->D2R;
532
                    break;      // used in merc and eqc
533
                case "lon_0": $this->long0 = $paramVal * Proj4php::$common->D2R;
534
                    break;       // lam0, central longitude
535
                case "alpha": $this->alpha = floatval( $paramVal ) * Proj4php::$common->D2R;
536
                    break;  //for somerc projection
537
                case "lonc": $this->longc = paramVal * Proj4php::$common->D2R;
538
                    break;       //for somerc projection
539
                case "x_0": $this->x0 = floatval( $paramVal );
540
                    break;  // false easting
541
                case "y_0": $this->y0 = floatval( $paramVal );
542
                    break;  // false northing
543
                case "k_0": $this->k0 = floatval( $paramVal );
544
                    break;  // projection scale factor
545
                case "k": $this->k0 = floatval( $paramVal );
546
                    break;  // both forms returned
547
                case "r_a": $this->R_A = true;
548
                    break;                 // sphere--area of ellipsoid
549
                case "zone": $this->zone = intval( $paramVal, 10 );
550
                    break;  // UTM Zone
551
                case "south": $this->utmSouth = true;
552
                    break;  // UTM north/south
553
                case "towgs84": $this->datum_params = explode( ",", $paramVal );
554
                    break;
555
                case "to_meter": $this->to_meter = floatval( $paramVal );
556
                    break; // cartesian scaling
557
                case "from_greenwich": $this->from_greenwich = $paramVal * Proj4php::$common->D2R;
558
                    break;
559
                // DGR 2008-07-09 : if pm is not a well-known prime meridian take
560
                // the value instead of 0.0, then convert to radians
561
                case "pm": $paramVal = trim( $paramVal );
562
                    $this->from_greenwich = Proj4php::$primeMeridian[$paramVal] ? Proj4php::$primeMeridian[$paramVal] : floatval( $paramVal );
563
                    $this->from_greenwich *= Proj4php::$common->D2R;
564
                    break;
565
                // DGR 2010-11-12: axis
566
                case "axis": $paramVal = trim( $paramVal );
567
                    $legalAxis = "ewnsud";
568
                    if( strlen( paramVal ) == 3 &&
569
                        strpos( $legalAxis, substr( $paramVal, 0, 1 ) ) !== false &&
570
                        strpos( $legalAxis, substr( $paramVal, 1, 1 ) ) !== false &&
571
                        strpos( $legalAxis, substr( $paramVal, 2, 1 ) ) !== false ) {
572
                        $this->axis = $paramVal;
573
                    } //FIXME: be silent ?
574
                    break;
575
                case "no_defs": break;
576
                default: //alert("Unrecognized parameter: " . paramName);
577
            } // switch()
578
        } // for paramArray
579
        $this->deriveConstants();
580
    }
581
 
582
    /**
583
     * Function: deriveConstants
584
     * Sets several derived constant values and initialization of datum and ellipse parameters.
585
     *
586
     */
587
    public function deriveConstants() {
588
 
589
        if( isset( $this->nagrids ) && $this->nagrids == '@null' )
590
            $this->datumCode = 'none';
591
 
592
        if( isset( $this->datumCode ) && $this->datumCode != 'none' ) {
593
 
594
            $datumDef = Proj4php::$datum[$this->datumCode];
595
 
596
            if( is_array($datumDef ) ) {
597
                $this->datum_params = array_key_exists( 'towgs84', $datumDef ) ? explode( ',', $datumDef['towgs84'] ) : null;
598
                $this->ellps = $datumDef['ellipse'];
599
                $this->datumName = array_key_exists( 'datumName', $datumDef ) ? $datumDef['datumName'] : $this->datumCode;
600
            }
601
        }
602
        if( !isset( $this->a ) ) {    // do we have an ellipsoid?
603
            if( !isset( $this->ellps ) || strlen( $this->ellps ) == 0 || !array_key_exists( $this->ellps, Proj4php::$ellipsoid ) )
604
                $ellipse = Proj4php::$ellipsoid['WGS84'];
605
            else {
606
                $ellipse = Proj4php::$ellipsoid[$this->ellps];
607
            }
608
 
609
            Proj4php::extend( $this, $ellipse );
610
        }
611
 
612
        if( isset( $this->rf ) && !isset( $this->b ) )
613
            $this->b = (1.0 - 1.0 / $this->rf) * $this->a;
614
 
615
        if ( (isset($this->rf) && $this->rf === 0) || abs($this->a - $this->b) < Proj4php::$common->EPSLN) {
616
            $this->sphere = true;
617
            $this->b = $this->a;
618
        }
619
        $this->a2 = $this->a * $this->a;          // used in geocentric
620
        $this->b2 = $this->b * $this->b;          // used in geocentric
621
        $this->es = ($this->a2 - $this->b2) / $this->a2;  // e ^ 2
622
        $this->e = sqrt( $this->es );        // eccentricity
623
        if( isset( $this->R_A ) ) {
624
            $this->a *= 1. - $this->es * (Proj4php::$common->SIXTH + $this->es * (Proj4php::$common->RA4 + $this->es * Proj4php::$common->RA6));
625
            $this->a2 = $this->a * $this->a;
626
            $this->b2 = $this->b * $this->b;
627
            $this->es = 0.0;
628
        }
629
        $this->ep2 = ($this->a2 - $this->b2) / $this->b2; // used in geocentric
630
        if( !isset( $this->k0 ) )
631
            $this->k0 = 1.0;    //default value
632
 
633
        //DGR 2010-11-12: axis
634
        if( !isset( $this->axis ) ) {
635
            $this->axis = "enu";
636
        }
637
 
638
        $this->datum = new Proj4phpDatum( $this );
639
    }
640
 
641
}