Comment connecter des cartes dans une projection ellipsoïde, si celle-ci n'est pas fournie?

Ou comment adapter les tuiles de carte Yandex à la projection OpenStreetMaps?

Entrée


Chaque fois que vous ouvrez une sorte de carte en ligne, vous ne la téléchargez pas dans son intégralité. Pour accélérer le chargement de la carte, celle-ci est divisée en petits morceaux (tuiles) afin que seule la zone souhaitée puisse être téléchargée. Le problème est que vous pouvez découper ces carrés de plusieurs manières.



La plupart des cartes en ligne «pensent» que la Terre a la forme d'une boule. Parmi eux, par exemple, Google maps et OpenStreetMaps. Certains, plus méticuleux, tiennent compte du fait que la planète n'est pas la bonne balle: au moins, elle est aplatie aux pôles. Une telle projection ellipsoïde est utilisée, par exemple, dans les cartes Yandex.



Par conséquent, une cellule avec le même nombre dans différentes projections affichera des endroits complètement différents. Par exemple, voici une tuile avec le numéro 10427 sur l'axe X, 5119 sur l'axe Y. Niveau d'échelle 14. À gauche - OSM, à droite Yandex. Au lieu d'une ville - une sorte de forêt.



Bien que la plupart des moteurs de carte puissent ajuster automatiquement les tuiles à la projection souhaitée, vous devrez parfois le faire manuellement. Mais comment? Le moyen le plus simple consiste simplement à décaler les tuiles d'un certain nombre de pixels. En conséquence, nous verrons la zone souhaitée sur la carte. Bien sûr, si vous regardez attentivement, vous pouvez voir des distorsions. Mais je pense tout de même que des tâches quotidiennes d'une précision similaire seront plus que suffisantes. Il est donc temps de terminer l'introduction et de commencer à faire le convertisseur.



Méthodologie


Pour fonctionner, nous avons besoin d'une formule de conversion. Pour autant que je sache, il a été tiré directement du code de la page Yandex Maps, à l'époque, alors qu'il était encore tout à fait possible de le faire. Je ne trouverai pas le lien vers la source pour le moment, mais cette formule a déjà été publiée sur le hub. Je ne l'ai pratiquement pas touché: je l'ai simplement réécrit sur Swift et j'ai donné des variables incompréhensibles à une lettre plus de noms «parlants». Au moins ceux d'entre eux qui ont pu s'identifier. (Merci à Erelen pour son aide)

Eh bien, la tâche est la suivante. Nous devons faire un convertisseur qui prend le nombre de tuiles dans la projection standard comme entrée, et le nombre de tuiles dans la projection ellipsoïde et le nombre de pixels par lesquels il doit être déplacé vers la sortie.

Alors. Par exemple, prenez une tuile portant le numéro X 10427, Y 5119, Z 14.

Nous agirons en deux temps. Tout d'abord, vous devez trouver les coordonnées (latitude et longitude) de cette tuile. Par exemple, les coordonnées de son coin supérieur gauche.

func tileNumberToCoordinates(tileX: Int, tileY: Int, mapZoom: Int) -> (lat_deg: Double, lon_deg: Double) { let n : Double = pow(2.0, Double(mapZoom)) let lon = (Double(tileX) / n) * 360.0 - 180.0 let lat = atan( sinh (.pi - (Double(tileY) / n) * 2 * Double.pi)) * (180.0 / .pi) return (lat, lon) } 

Nous obtenons la sortie (55.7889 49.1088). Maintenant, nous substituons les valeurs obtenues dans notre formule. Le niveau de zoom est toujours le même: 14e.

 func getWGS84Position(latitude: Double, longitude: Double, zoom: Int) -> (x:Int, y:Int, offsetX:Int, offsetY:Int) { // Earth vertical and horisontal radiuses let radiusA = 6378137.0 let radiusB = 6356752.0 let latitudeInRadians = latitude * Double.pi / 180 let yCompressionOfEllipsoid = sqrt( pow(radiusA, 2.0) - pow(radiusB, 2.0)) / radiusA // I really don't know what the name of this variable mean =( let m2 = log((1 + sin(latitudeInRadians)) / (1 - sin(latitudeInRadians))) / 2 - yCompressionOfEllipsoid * log((1 + yCompressionOfEllipsoid * sin(latitudeInRadians)) / (1 - yCompressionOfEllipsoid * sin(latitudeInRadians))) / 2 // x count = y count let xTilesCountForThisZoom = Double(1 << zoom) //Tile numbers in WGS-84 proection let xTileNumber = floor((longitude + 180) / 360 * xTilesCountForThisZoom) let yTileNumber = floor(xTilesCountForThisZoom / 2 - m2 * xTilesCountForThisZoom / 2 / Double.pi) //Offset in pixels of the coordinate of the //left-top corner of the OSM tile //from the left-top corner of the WGS-84 tile let offsetX = floor(((longitude + 180) / 360 * xTilesCountForThisZoom - xTileNumber) * 256) let offsetY = floor(((xTilesCountForThisZoom / 2 - m2 * xTilesCountForThisZoom / 2 / Double.pi) - yTileNumber) * 256) return (Int(xTileNumber), Int(yTileNumber), Int(offsetX), Int(offsetY)) } 

Nous obtenons (10427, 5133, 0, 117). Cela signifie que nous avons besoin d'une tuile Yandex avec le numéro X 10427, Y 5133, Z 14. Et si vous la déplacez de 0 pixel vers la gauche et 117 pixels vers le haut, alors elle prendra la bonne place.



Et que faire?


Si vous écrivez votre navigateur et que vous avez la possibilité d'influencer l'affichage de la carte, alors tout est relativement simple. Calculez le décalage pour l'une des tuiles à l'écran. Et déplacez l'élément avec la carte de ce nombre de pixels de n'importe quelle manière pratique.

Mais si vous n'avez pas accès au code, vous devrez saisir un lien intermédiaire entre le serveur de carte et le navigateur. Par exemple, j'ai créé un serveur simple pour cela. Il prend l'entrée du nombre de la tuile souhaitée, calcule le nombre de la tuile dans la projection ellipsoïde. Il télécharge également trois tuiles voisines. Colle ces quatre tuiles en une grande, puis en découpe le fragment souhaité et le renvoie au navigateur de l'utilisateur.



Résultat et original:



Bien entendu, toutes ces opérations nécessitent des coûts de temps supplémentaires. En utilisant ces liens, vous pouvez évaluer la vitesse à laquelle le serveur «photoshop» la carte en temps réel:

https://anygis.ru/api/v1/Yandex_map/{xasket/{y}/{z}

https://anygis.ru/api/v1/Yandex_sat_clean/{xasket/{y}/{z}

Eh bien, j'espère que les informations présentées ici sont utiles à quelqu'un. Bonne chance avec vos expériences.

Source: https://habr.com/ru/post/fr483120/


All Articles