Subversion Repositories eFlore/Applications.cel

Rev

Rev 2747 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

<?php
class CelStreets extends Cel {

        /**
         * Méthode appelée avec une requête de type GET.
         */
        public function getRessource() {
                $this->getStreetGeom();
        }
        
        private function getStreetGeom() {
                
                $erreurs = $this->verifierParametres();
                if(!empty($erreurs)) {
                        $this->envoyerMessageErreur(400, implode("\n", $erreurs));
                        exit;
                }
                
                $latitude_debut = $_GET['latitude_debut'];
                $longitude_debut = $_GET['longitude_debut'];
                $latitude_fin = $_GET['latitude_fin'];
                $longitude_fin = $_GET['longitude_fin'];
                
                $dist_max = round(self::haversineGreatCircleDistance($latitude_debut, $longitude_debut, $latitude_fin, $longitude_fin)/2);

                $url_tpl = $this->getUrlRequeteOverPass().urlencode($this->getRequetePasLoin($latitude_debut, $longitude_debut, $latitude_fin, $longitude_fin, $dist_max));
                $json = file_get_contents($url_tpl);
                
                $infos = json_decode($json, true);
                $this->filtrerNoeuds($infos, $latitude_debut, $longitude_debut, $latitude_fin, $longitude_fin);
                
                header('Content-type: application/json');
                echo json_encode($infos); exit;
        }
        
        private function verifierParametres() {
                $parametres_obligatoires = array('latitude_debut', 'longitude_debut', 'latitude_fin', 'longitude_fin');
                $erreurs = array();
                foreach($parametres_obligatoires as $param) {
                        if(empty($_GET[$param]) || !is_numeric($_GET[$param])) {
                                $erreurs[] = "Le paramètre $param est obligatoire et doit être un flottant";
                        }
                }
                return $erreurs;
        }
        
        function getUrlRequeteOverPass() {
                return $this->config['cel']['url_service_rue'];
        }
        
        function getRequetePasLoin($latitude_debut, $longitude_debut, $latitude_fin, $longitude_fin, $dist = 50) {
                $req = 'way["highway"]
                        (around:'.$dist.','.$latitude_debut.','.$longitude_debut.')
                        (around:'.$dist.','.$latitude_fin.','.$longitude_fin.')
                        ;
                        (
                        ._;
                        >;
                        );
                out geom;';
                
                return $req;
        }
        
        function getRequete($nord, $sud, $est, $ouest) {                
                return '(
                        way["highway"]
                        ('.$sud.','.$ouest.','.$nord.','.$est.');
                        >;
                );out geom;';
        }
        
        function filtrerNoeuds(&$infos, $latitude_debut, $longitude_debut, $latitude_fin, $longitude_fin) {     
                $distances_debut = array();
                $distances_fin = array();
                
                foreach($infos['elements'] as $index_e => &$element) {
                        if(empty($element['geometry'])) {
                                unset($infos['elements'][$index_e]);
                        } else {
                                $index_fin_tableau = count($element['geometry']) - 1;
                                
                                $distances_debut[$index_e] = -1;
                                $distances_fin[$index_e] = -1;
                                
                                $index_point_plus_proche_debut = 0;
                                $dist_point_plus_proche_debut = 999999999;
                                $index_point_plus_proche_fin = $index_fin_tableau;
                                $dist_point_plus_proche_fin = 9999999999;
                                
                                $index_debut = array(0, 1);
                                $index_fin = array($index_fin_tableau -1, $index_fin_tableau);
                                
                                $element['geometryOrig'] = $element['geometry'];
                                
                                foreach($element['geometry'] as $index_g => &$geometry) {                                       
                                        // Calcul de la plus petite distance de la droite de rue au point
                                        // pour sélectionner la rue la plus proche des deux points
                                        if($index_g + 1 < count($element['geometry'])) {
                                                $geom_next = $element['geometry'][$index_g + 1];
                                                $this->stockerDistancePlusCourte($distances_debut, $index_debut, $index_e, $index_g, $geometry, $geom_next, $latitude_debut, $longitude_debut);
                                                $this->stockerDistancePlusCourte($distances_fin, $index_fin, $index_e, $index_g, $geometry, $geom_next, $latitude_fin, $longitude_fin);
                                        }
                                        
                                        $dist_debut= self::haversineGreatCircleDistance($latitude_debut, $longitude_debut, $geometry['lat'], $geometry['lon']);
                                        $dist_fin = self::haversineGreatCircleDistance($latitude_fin, $longitude_fin, $geometry['lat'], $geometry['lon']);
                                        if($dist_point_plus_proche_debut > $dist_debut) {
                                                $dist_point_plus_proche_debut = $dist_debut;
                                                $index_point_plus_proche_debut = $index_g;
                                        }
                                        
                                        if($dist_point_plus_proche_fin > $dist_fin) {
                                                $dist_point_plus_proche_fin = $dist_fin;
                                                $index_point_plus_proche_fin = $index_g;
                                        }
                                }
                                
                                $index_min = min($index_point_plus_proche_debut, $index_point_plus_proche_fin);
                                $index_max = max($index_point_plus_proche_debut, $index_point_plus_proche_fin);
                                                                
                                $infos_debut = array('lat' => (float)$latitude_debut, 'lon' => (float)$longitude_debut);
                                $infos_fin = array('lat' => (float)$latitude_fin, 'lon' => (float)$longitude_fin);
                                
                                // Inversion des points de début et de fin si le début donné par l'utilisateur est plus 
                                // proche de la fin
                                if($index_min != $index_point_plus_proche_debut) {
                                        $tmp = $infos_debut;
                                        $infos_debut = $infos_fin;
                                        $infos_fin = $tmp;
                                }
                                                                
                                // Projection des points de début et d'arrivée sur le segment de rue
                                if($index_min < $index_fin_tableau) {
                                        $proj_debut = $this->getOrthoPoint($element['geometry'][$index_min], $element['geometry'][$index_min+1], $infos_debut);
                                        $index_insertion_point_debut = $this->getIndexInsertionDebut($element, $index_min, $infos_debut);
                                } else {
                                        $proj_debut = $infos_debut;
                                        $index_insertion_point_debut = 0;
                                }
                                
                                if($index_max > 0) {
                                        $proj_fin = $this->getOrthoPoint($element['geometry'][$index_max], $element['geometry'][$index_max-1], $infos_fin);
                                        $index_insertion_point_fin = $this->getIndexInsertionFin($element, $index_max, $infos_fin);
                                } else {
                                        $proj_fin = $infos_fin;
                                        $index_insertion_point_fin = $index_fin_tableau;
                                }

                                $index_insertion_point_debut = $index_insertion_point_debut < 0 ? 0 : $index_insertion_point_debut;
                                $index_insertion_point_fin = $index_insertion_point_fin > $index_fin_tableau ? $index_fin_tableau : $index_insertion_point_fin;
                                
                                $rd = array();
                                for($ii = $index_insertion_point_debut+1; $ii <= $index_insertion_point_fin; $ii++) {
                                        $rd[] = $element['geometry'][$ii];
                                }
                                
                                // Insertion des points utilisateurs en début et fin de tableau
                                array_unshift($rd, $proj_debut);
                                array_push($rd, $proj_fin);
                                
                                $element['geometry'] = $rd;
                        }
                }
                
                if(!empty($distances_debut)) {
                        asort($distances_debut);
                        reset($distances_debut);
                        $cle_plus_courte_debut = key($distances_debut);
                }
                
                if(!empty($distances_fin)) {
                        asort($distances_fin);
                        reset($distances_fin);
                        $cle_plus_courte_fin = key($distances_fin);
                }
                
                $cle_choisie = $this->choisirIndexRuePlusProchePourDebutEtFin($cle_plus_courte_debut, $cle_plus_courte_fin, $distances_debut, $distances_fin);  
                $rue_plus_proche = $distances_fin[$cle_choisie];                        
                unset($distances_fin[$cle_choisie]);
                $distances_fin = array($cle_choisie => $rue_plus_proche) + $distances_fin;
                
                $elements_fmt = array();
                foreach($distances_fin as $index => $distance) {
                        //echo '<pre>'.print_r($infos['elements'][$index], true).'</pre>';exit;
                        $rue_proche = $infos['elements'][$index];
                        $rue_proche['dist_min'] = $distance;
                        $elements_fmt[] = $rue_proche;
                }
                $infos['elements'] = $elements_fmt;
        }
        
        function choisirIndexRuePlusProchePourDebutEtFin($cle_plus_courte_debut, $cle_plus_courte_fin, $distances_debut, $distances_fin) {
                
                $cle_choisie = $cle_plus_courte_fin;
                // Si la rue la plus proche n'est pas la même pour les deux points
                if($cle_plus_courte_debut != $cle_plus_courte_fin) {
                        // on calcule celle qui est la mieux positionnée en moyenne
                        $index_debut_tab_debut = array_search($cle_plus_courte_debut, array_keys($distances_debut));
                        $index_debut_tab_fin = array_search($cle_plus_courte_debut, array_keys($distances_fin));
                        
                        $index_fin_tab_debut = array_search($cle_plus_courte_fin, array_keys($distances_debut));
                        $index_fin_tab_fin = array_search($cle_plus_courte_fin, array_keys($distances_fin));
                        
                        $moy_index_debut = ($index_debut_tab_debut + $index_debut_tab_fin)/2;
                        $moy_index_fin = ($index_fin_tab_debut + $index_fin_tab_fin)/2;
                        
                        $cle_choisie = ($moy_index_debut < $moy_index_fin) ? $cle_plus_courte_debut : $cle_plus_courte_fin;
                        
                        // Si ça ne suffit pas on prend celle qui a la distance moyenne la plus courte aux deux points
                        if ($moy_index_debut ==  $moy_index_fin) {
                                $moyenne_dist_cle_debut = ($distances_debut[$cle_plus_courte_debut] + $distances_fin[$cle_plus_courte_debut])/2;
                                $moyenne_dist_cle_fin = ($distances_debut[$cle_plus_courte_fin] + $distances_fin[$cle_plus_courte_fin])/2;
                                        
                                $cle_choisie =  ($moyenne_dist_cle_debut < $moyenne_dist_cle_fin) ? $cle_plus_courte_debut : $cle_plus_courte_fin;
                        }
                }
                
                return $cle_choisie;
        }
        
        function getIndexInsertionDebut($element, $index_min, $debut) {
                // le point de début est situé entre $index_debut et $index_debut + 1
                $proj_debut = $this->getOrthoPoint($element['geometry'][$index_min], $element['geometry'][$index_min+1], $debut);               
                $point_deb = $element['geometry'][$index_min];
                $point_deb_plus_un = $element['geometry'][$index_min+1];
                $distance_point_deb_plus_un_proj = self::haversineGreatCircleDistance($point_deb_plus_un['lat'], $point_deb_plus_un['lon'], $proj_debut['lat'], $proj_debut['lon']);
                $distance_point_deb_deb_plus_un = self::haversineGreatCircleDistance($point_deb['lat'], $point_deb['lon'], $point_deb_plus_un['lat'], $point_deb_plus_un['lon']);
                
                if($distance_point_deb_plus_un_proj < $distance_point_deb_deb_plus_un) {
                        // le point le plus proche doit être éliminé
                        return $index_min + 1;
                } else {
                        // il doit être gardé
                        return $index_min;      
                }
        }
        
        function getIndexInsertionFin($element, $index_max, $fin) {
                $proj_fin = $this->getOrthoPoint($element['geometry'][$index_max], $element['geometry'][$index_max-1], $fin);
                $point_fin = $element['geometry'][$index_max];
                $point_fin_moins_un = $element['geometry'][$index_max-1];
                $distance_point_fin_moins_un_proj = self::haversineGreatCircleDistance($point_fin_moins_un['lat'], $point_fin_moins_un['lon'], $proj_fin['lat'], $proj_fin['lon']);
                $distance_point_fin_fin_moins_un = self::haversineGreatCircleDistance($point_fin['lat'], $point_fin['lon'], $point_fin_moins_un['lat'], $point_fin_moins_un['lon']);
        
                if($distance_point_fin_moins_un_proj < $distance_point_fin_fin_moins_un) {
                        // le point le plus proche doit être éliminé
                        return $index_max;
                } else {
                        // il doit être gardé
                        return $index_max + 1;
                }
        }
        
        function stockerDistancePlusCourte(&$distances, &$infos_index_position_point, $index_e, $index_g, $geometry, $geom_next, $latitude_point, $longitude_point) {
                $a = $geometry['lat'];
                $b = $geometry['lon'];
                $c = $geom_next['lat'];
                $d = $geom_next['lon'];
                $x = $latitude_point ;
                $y = $longitude_point ;
                $dist = $this->getShortestDistance($a, $b, $c, $d, $x, $y);
                if($distances[$index_e] == -1 || $dist < $distances[$index_e]) {
                        $distances[$index_e] = $dist;
                        $infos_index_position_point = array($index_g, $index_g+1);
                }
        }
        
        // projette le point C sur le segment AB
        function getOrthoPoint($a, $b, $c) {            
                $x1 = $a['lat']; 
                $y1 = $a['lon']; 
                $x2 = $b['lat']; 
                $y2 = $b['lon']; 
                $x3 = $c['lat']; 
                $y3 = $c['lon'];
                $px = $x2-$x1;
                $py = $y2-$y1;
                $dAB = $px*$px + $py*$py;
                $u = (($x3 - $x1) * $px + ($y3 - $y1) * $py) / $dAB;
                $x = $x1 + $u * $px;
                $y = $y1 + $u * $py;
                return array('lat' => $x, 'lon' => $y);  
        }
        
        function getShortestDistance($a, $b, $c, $d, $x, $y) {
                //Coordinates are (a,b) and (c,d)
                //the point (x,y) is the required point.
                //echo 'nagi ';
                //echo $c.' - '.$a." = ".($c-$a).'<br />';
                if(($c-$a) == 0) {
                        return 56465465456485;
                }
                
                $m=($d-$b)/($c-$a);
                //echo $m."\n";
                
                //echo $y-($m*$x)-$b+($m*$a)."\n";
                $distance=abs($y-($m*$x)-$b+($m*$a))/sqrt(1+($m*$m));
                return  $distance;
        }
        
        function haversineGreatCircleDistance(
                        $latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo, $earthRadius = 6371000)
        {
                // convert from degrees to radians
                $latFrom = deg2rad($latitudeFrom);
                $lonFrom = deg2rad($longitudeFrom);
                $latTo = deg2rad($latitudeTo);
                $lonTo = deg2rad($longitudeTo);
        
                $latDelta = $latTo - $latFrom;
                $lonDelta = $lonTo - $lonFrom;
        
                $angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) +
                                cos($latFrom) * cos($latTo) * pow(sin($lonDelta / 2), 2)));
                return $angle * $earthRadius;
        }
        
        public static function vincentyGreatCircleDistance($latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo, $earthRadius = 6371000)
        {
                // convert from degrees to radians
                $latFrom = deg2rad($latitudeFrom);
                $lonFrom = deg2rad($longitudeFrom);
                $latTo = deg2rad($latitudeTo);
                $lonTo = deg2rad($longitudeTo);
        
                $lonDelta = $lonTo - $lonFrom;
                $a = pow(cos($latTo) * sin($lonDelta), 2) +
                pow(cos($latFrom) * sin($latTo) - sin($latFrom) * cos($latTo) * cos($lonDelta), 2);
                $b = sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lonDelta);
        
                $angle = atan2(sqrt($a), $b);
                return $angle * $earthRadius;
        }
}
?>