app/include/iTagLauncher.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php
/*
 * Copyright 2013 Jérôme Gasperi
 *
 * Licensedunder the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
class iTagLauncher
{
    

    /**
     * Constructor
     *
     * @param array $config
     */
    public function __construct($config)
    {
        $this->tag($config);
    }
    
    /**
     * Tag geometry
     *
     *  @OA\Get(
     *      path="/",
     *      summary="Tag a geometry",
     *      description="Returns a list of features intersecting input geometry",
     *      @OA\Parameter(
     *         name="geometry",
     *         in="query",
     *         required=true,
     *         description="Input geometry as a POLYGON WKT",
     *         @OA\Schema(
     *             type="string"
     *         )
     *      ),
     *      @OA\Parameter(
     *         name="taggers",
     *         in="query",
     *         required=true,
     *         description="List of tagger applied. You can specify multiple taggers comma separated
* geology : Return intersected geological features i.e. faults, glaciers, plates and volcanoes
* hydrology : Return intersected hydrological features i.e. Lakes and rivers
* landcover : Compute landcover (based on Global LandCover 2000)
* physical : Return physical intersected features i.e. marine regions
* political : Return political intersected features i.e. continents, countries, regions and states
* population : Compute population count and density",
     *         @OA\Schema(
     *             type="enum",
     *             enum={"geology", "hydrology", "landcover", "physical", "political", "population"}
     *         )
     *      ),
     *      @OA\Parameter(
     *         name="timestamp",
     *         in="query",
     *         required=false,
     *         description="Input timestamp (to compute season based on geometry location) - format ISO 8601 YYYY-MM-DDTHH:MM:SS",
     *         @OA\Schema(
     *             type="string"
     *         )
     *      ),
     *      @OA\Parameter(
     *         name="planet",
     *         in="query",
     *         required=false,
     *         description="Input timestamp (to compute season based on geometry location) - format ISO 8601 YYYY-MM-DDTHH:MM:SS",
     *         @OA\Schema(
     *             type="string"
     *         )
     *      ),
     *      @OA\Parameter(
     *         name="_pretty",
     *         in="query",
     *         required=false,
     *         description="True to return pretty print response",
     *         @OA\Schema(
     *             type="string"
     *         )
     *      ),
     *      @OA\Parameter(
     *         name="_wkt",
     *         in="query",
     *         required=false,
     *         description="True to return intersected features geometries as WKT",
     *         @OA\Schema(
     *             type="string"
     *         )
     *      ),
     *      @OA\Response(
     *          response="200",
     *          description="List of features",
     *          @OA\JsonContent(
     *               example={
     *                   "geometry": "POLYGON((6.487426757812523 45.76081241294796,6.487426757812523 46.06798615804025,7.80578613281244 46.06798615804025,7.80578613281244 45.76081241294796,6.487426757812523 45.76081241294796))",
     *                   "planet": "earth",
     *                   "timestamp": "2018-01-13",
     *                   "area_unit": "km2",
     *                   "cover_unit": "%",
     *                   "content": {
     *                       "area": 3483.53511,
     *                       "keywords": {
     *                           "location_northern",
     *                           "season_winter"
     *                       },
     *                       "political": {
     *                           "continents": {
     *                               {
     *                                   "name": "Europe",
     *                                   "id": "continent_europe_6255148",
     *                                   "countries": {
     *                                       {
     *                                           "name": "Italy",
     *                                           "id": "country_italy_3175395",
     *                                           "pcover": 37.02,
     *                                           "gcover": 0.42,
     *                                           "regions": {
     *                                               {
     *                                                   "name": "Valle d'Aosta",
     *                                                   "id": "region_valledaosta_3164857",
     *                                                   "pcover": 37.2,
     *                                                   "gcover": 39.19,
     *                                                   "states": {
     *                                                       {
     *                                                           "name": "Aoste",
     *                                                           "id": "state_aoste_3182996",
     *                                                           "pcover": 37.02,
     *                                                           "gcover": 39.13
     *                                                       }
     *                                                   }
     *                                               }
     *                                           }
     *                                       },
     *                                       {
     *                                           "name": "France",
     *                                           "id": "country_france_3017382",
     *                                           "pcover": 32.9,
     *                                           "gcover": 0.18,
     *                                           "regions": {
     *                                               {
     *                                                   "name": "Rh\u00f4ne-Alpes",
     *                                                   "id": "region_rhonealpes_11071625",
     *                                                   "pcover": 32.94,
     *                                                   "gcover": 2.56,
     *                                                   "states": {
     *                                                       {
     *                                                           "name": "Haute-Savoie",
     *                                                           "id": "state_hautesavoie_3013736",
     *                                                           "pcover": 29.39,
     *                                                           "gcover": 21.86
     *                                                       }
     *                                                   }
     *                                               },
     *                                               {
     *                                                   "name": "Rh\u00f4ne-Alpes",
     *                                                   "id": "region_rhonealpes_11071625",
     *                                                   "pcover": 32.94,
     *                                                   "gcover": 2.56,
     *                                                   "states": {
     *                                                       {
     *                                                           "name": "Savoie",
     *                                                           "id": "state_savoie_2975517",
     *                                                           "pcover": 3.51,
     *                                                           "gcover": 1.98
     *                                                       }
     *                                                   }
     *                                               }
     *                                           }
     *                                       },
     *                                       {
     *                                           "name": "Switzerland",
     *                                           "id": "country_switzerland_2658434",
     *                                           "pcover": 30.04,
     *                                           "gcover": 2.53,
     *                                           "regions": {
     *                                               {
     *                                                   "states": {
     *                                                       {
     *                                                           "name": "Valais",
     *                                                           "id": "state_valais_2658205",
     *                                                           "pcover": 30.04,
     *                                                           "gcover": 19.79
     *                                                       }
     *                                                   }
     *                                               }
     *                                           }
     *                                       }
     *                                   }
     *                               }
     *                           }
     *                       },
     *                       "geology": {
     *                           "glaciers": {
     *                               {
     *                                   "name": "La Vall\u00e9e Blanche"
     *                               },
     *                               {
     *                                   "name": "Zmuttgletscher",
     *                                   "geometry": "POLYGON((7.74563268097009 46.0679861580402,7.80578613281244 45.8917863902736,7.22527102956286 45.9083519552024,7.29786217539623 46.0250511739524,7.39771569102123 45.9386253927024,7.34168636250942 46.0679861580402,7.74563268097009 46.0679861580402))"
     *                               }
     *                           },
     *                           "faults": {
     *                               {
     *                                   "name": "Tectonic Contact",
     *                                   "geometry": "LINESTRING(6.89865119793091 45.760812412948,7.22627733871568 46.0679861580402)"
     *                               },
     *                               {
     *                                   "name": "Tectonic Contact",
     *                                   "geometry": "LINESTRING(7.31459581150975 45.760812412948,7.80578613281244 46.0036258690342)"
     *                               }
     *                           },
     *                           "plates": {
     *                               {
     *                                   "name": "Aoste",
     *                                   "geometry": "POLYGON((7.02208256000011 45.925259909,7.64302657100012 45.966342672,7.80578613281244 45.760812412948,6.78449660332847 45.760812412948,7.02208256000011 45.925259909))"
     *                               },
     *                               {
     *                                   "name": "Haute-Savoie",
     *                                   "geometry": "POLYGON((7.02208256000011 45.925259909,6.69589146278248 45.760812412948,6.48742675781252 45.8867125682433,6.48742675781252 46.0679861580402,7.02208256000011 45.925259909))"
     *                               },
     *                               {
     *                                   "name": "Savoie",
     *                                   "geometry": "MULTIPOLYGON(((6.48742675781252 45.8867125682433,6.69589146278248 45.760812412948,6.48742675781252 45.760812412948,6.48742675781252 45.8867125682433)))"
     *                               },
     *                               {
     *                                   "name": "Valais",
     *                                   "geometry": "POLYGON((7.80578613281244 45.9184668986572,7.09019209700011 45.8805081180001,6.85196374500009 46.0646829220001,7.80578613281244 46.0679861580402,7.80578613281244 45.9184668986572))"
     *                               }
     *                           }
     *                       },
     *                       "hydrology": {
     *                           "rivers": {
     *                               {
     *                                   "name": "Dora Baltea",
     *                                   "geometry": "MULTILINESTRING((6.88274173268792 45.8056094421815,7.07094960396669 45.760812412948),(7.47528042859518 45.760812412948,7.49574465227324 45.760812412948),(7.5077323410963 45.760812412948,7.55451062324413 45.760812412948))"
     *                               }
     *                           }
     *                       }
     *                   },
     *                   "references": {
     *                       {
     *                           "dataset": "Admin level 0 - Countries",
     *                           "author": "Natural Earth",
     *                           "license": "Free of Charge",
     *                           "url": "http:\/\/www.naturalearthdata.com\/downloads\/10m-cultural-vectors\/10m-admin-0-countries\/"
     *                       },
     *                       {
     *                           "dataset": "Admin level 1 - States, Provinces",
     *                           "author": "Natural Earth",
     *                           "license": "Free of Charge",
     *                           "url": "http:\/\/www.naturalearthdata.com\/downloads\/10m-cultural-vectors\/10m-admin-1-states-provinces\/"
     *                       },
     *                       {
     *                           "dataset": "World Glacier Inventory",
     *                           "author": "NSIDC",
     *                           "license": "Free of Charge",
     *                           "url": "http:\/\/nsidc.org\/data\/docs\/noaa\/g01130_glacier_inventory\/#data_descriptions"
     *                       },
     *                       {
     *                           "dataset": "Major world fault lines",
     *                           "author": "ESRI",
     *                           "license": "Access granted to Licensee only",
     *                           "url": "http:\/\/edcommunity.esri.com\/Resources\/Collections\/mapping-our-world"
     *                       },
     *                       {
     *                           "dataset": "Major world tectonic plates",
     *                           "author": "ESRI",
     *                           "license": "Access granted to Licensee only",
     *                           "url": "http:\/\/edcommunity.esri.com\/Resources\/Collections\/mapping-our-world"
     *                       },
     *                       {
     *                           "dataset": "Major volcanos of the world",
     *                           "author": "ESRI",
     *                           "license": "Access granted to Licensee only",
     *                           "url": "http:\/\/edcommunity.esri.com\/Resources\/Collections\/mapping-our-world"
     *                       },
     *                       {
     *                           "dataset": "Glaciated area",
     *                           "author": "Natural Earth",
     *                           "license": "Free of Charge",
     *                           "url": "http:\/\/www.naturalearthdata.com\/downloads\/10m-physical-vectors\/10m-glaciated-areas\/"
     *                       },
     *                       {
     *                           "dataset": "Rivers and lake centerlines",
     *                           "author": "Natural Earth",
     *                           "license": "Free of charge",
     *                           "url": "http:\/\/www.naturalearthdata.com\/downloads\/10m-physical-vectors\/10m-rivers-lake-centerlines\/"
     *                       },
     *                       {
     *                           "dataset": "Marine Regions",
     *                           "author": "Natural Earth",
     *                           "license": "Free of charge",
     *                           "url": "http:\/\/www.naturalearthdata.com\/downloads\/10m-physical-vectors\/10m-physical-labels\/"
     *                       }
     *                   }
     *               }
     *          )
     *      )
     *  )
     *
     * @param array params
     */
    private function tag($config)
    {
        $params = $this->getParams();

        try {
            
            /*
             * Superseed with input params
             */
            foreach (array_keys($params['config']) as $key) {
                if ( isset($params['config'][$key]) && $params['config'][$key] !== '' ) {
                    $config['general'][$key] = $params['config'][$key];
                }
            }

            /*
             * Instantiate iTag
             */
            $this->itag = new iTag($config['database'], $config['general']);

            $this->answer($this->json_format($this->itag->tag($params['metadata'], $params['taggers'])), 200);
        
        } catch (Exception $e) {
            $this->answer($this->json_format(array('ErrorMessage' => $e->getMessage(), 'ErrorCode' =>  $e->getCode())), $e->getCode());
        }

    }
    
    /**
     * Return GET params
     */
    private function getParams()
    {

        // Input query
        $query = $this->sanitize(filter_input_array(INPUT_GET));

        $this->pretty = isset($query['_pretty']) ? filter_var($query['_pretty'], FILTER_VALIDATE_BOOLEAN) : false;

        /*
         * Taggers
         */
        $taggers = array();

        if ( isset($query['taggers']) ) {

            $taggersList = explode(',', rawurldecode($query['taggers']));

            foreach (array_values($taggersList) as $value) {

                $taggerName = ucfirst(strtolower(trim($value)));
                $taggerOptions = array();

                foreach (array_keys($query) as $key ) {
                    $exploded = explode('_', $key);
                    if ( count($exploded) === 2 && ucfirst(strtolower(trim($exploded[0]))) === $taggerName ) {
                        $taggerOptions[$exploded[1]] = $this->formatInput($query[$key]);
                    }
                }

                $taggers[$taggerName] = $taggerOptions;

            }

        }
        
        $params = array(
            'metadata' => array(
                'geometry' => rawurldecode(filter_input(INPUT_GET, 'geometry', FILTER_UNSAFE_RAW)),
                'timestamp' => rawurldecode(filter_input(INPUT_GET, 'timestamp', FILTER_UNSAFE_RAW))
            ),
            'taggers' => $taggers,
            'config' => array(
                'returnGeometries' => isset($query['_wkt']) ? filter_var($query['_wkt'], FILTER_VALIDATE_BOOLEAN) : false,
                'planet' => rawurldecode(filter_input(INPUT_GET, 'planet', FILTER_UNSAFE_RAW))
            )
        );
        
        return $params;
    }
    
    /**
     * Stream HTTP result and exit
     */
    private function answer($response, $responseStatus)
    {
        
        /*
         * HTTP 1.1 headers
         */
        header('HTTP/1.1 ' . $responseStatus . ' ' . ($responseStatus === 200 ? 'OK' : 'Internal Server Error'));
        header('Cache-Control:  no-cache');
        header('Content-Type: application/json');
        
        /*
         * Set headers including cross-origin resource sharing (CORS)
         * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
         */
        $this->setCORSHeaders();
        
        /*
         * Stream data
         */
        echo $response;
    }
    
    /**
     * Set CORS headers (HTTP OPTIONS request)
     */
    private function setCORSHeaders()
    {
        $httpOrigin = filter_input(INPUT_SERVER, 'HTTP_ORIGIN', FILTER_UNSAFE_RAW);
        $httpRequestMethod = filter_input(INPUT_SERVER, 'HTTP_ACCESS_CONTROL_REQUEST_METHOD', FILTER_UNSAFE_RAW);
        $httpRequestHeaders = filter_input(INPUT_SERVER, 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS', FILTER_UNSAFE_RAW);
        
        /*
         * Only set access to known servers
         */
        if (isset($httpOrigin)) {
            header('Access-Control-Allow-Origin: ' . $httpOrigin);
            header('Access-Control-Allow-Credentials: true');
            header('Access-Control-Max-Age: 3600');
        }

        /*
         * Control header are received during OPTIONS requests
         */
        if (isset($httpRequestMethod)) {
            header('Access-Control-Allow-Methods: GET, OPTIONS');
        }
        if (isset($httpRequestHeaders)) {
            header('Access-Control-Allow-Headers: ' . $httpRequestHeaders);
        }
    }
 
    /**
     * Format a flat JSON string to make it more human-readable
     *
     * @param array $json JSON as an array
     *
     * @return string Indented version of the original JSON string
     */
    private function json_format($json)
    {

        /*
         * No pretty print - easy part
         */
        if (!$this->pretty) {
            return json_encode($json);
        }
        
        return json_encode($json, JSON_PRETTY_PRINT);
    }

    /**
     * Sanitize input parameter to avoid code injection
     *   - remove html tags
     *
     * @param {String or Array} $strOrArray
     */
    private function sanitize($strOrArray)
    {
        if (!isset($strOrArray)) {
            return null;
        }

        if (is_array($strOrArray)) {
            $result = array();
            foreach ($strOrArray as $key => $value) {
                $result[$key] = $this->sanitizeString($value);
            }
            return $result;
        }

        return $this->sanitizeString($strOrArray);
    }

    /**
     * Sanitize string
     *
     * @param string $str
     * @return string
     */
    private function sanitizeString($str)
    {

        /*
         * Remove html tags and NULL (i.e. \0)
         */
        if (is_string($str)) {

            /*
             * No Hexadecimal allowed i.e. nothing that starts with 0x
             */
            if (strlen($str) > 1 && substr($str, 0, 2) === '0x') {
                return null;
            }

            return strip_tags(str_replace(chr(0), '', $str));
        }

        /*
         * Let value untouched
         */
        return $str;
    }

    /**
     * Format input string
     *
     * @param string $str
     * @return mixed
     */
    private function formatInput($str)
    {

        if ( !$str ) {
            return $str;
        }

        if ( ctype_digit($str) ) {
            return (integer) $str;
        }

        switch (strtolower($str)) {

            case 'true':
                return true;

            case 'false':
                return false;

            default:
                return $str;
        }

    }

}