AuswÀhlen, Zwischenspeichern und Anzeigen von Fotos auf der Karte

In diesem Artikel habe ich beschlossen zu beschreiben, wie die FunktionalitÀt zum AuswÀhlen und Anzeigen von Fotos an einem bestimmten Ort auf der Karte in unserem Fotoservice gfranq.com implementiert wurde . Der Fotoservice funktioniert jetzt nicht.



Da wir viele Fotos in unserem Service hatten und jedes Mal, wenn die Änderungen im Ansichtsfenster zu ressourcenintensiv waren, Anfragen an die Datenbank sendeten, war es logisch, die Karte in mehrere Bereiche zu unterteilen, die Informationen zu den abgerufenen Daten enthalten. Aus offensichtlichen GrĂŒnden haben diese Bereiche eine rechteckige Form (obwohl auch ein sechseckiges Gitter berĂŒcksichtigt wurde). Da die Bereiche in großem Maßstab sphĂ€rischer werden, wurden auch Elemente der sphĂ€rischen Geometrie und Werkzeuge dafĂŒr berĂŒcksichtigt.


In diesem Artikel wurden folgende Probleme angesprochen:


  • Speichern und Abrufen von Fotos aus der Datenbank und Zwischenspeichern auf dem Server (SQL, C #, ASP.NET).
  • Laden Sie die erforderlichen Fotos auf der Clientseite herunter und speichern Sie sie im Client-Cache (JavaScript).
  • Neuberechnung von Fotos, die ausgeblendet oder angezeigt werden mĂŒssen, wenn sich das Ansichtsfenster Ă€ndert.
  • Elemente der sphĂ€rischen Geometrie.

Inhalt



Serverteil


Die folgenden Methoden zum AuswÀhlen und Speichern von Geoinformationen in der Datenbank wurden entwickelt:


  • Integrierter Geografiedatentyp von SQL Server.
  • Normale Auswahl mit EinschrĂ€nkungen.
  • ZusĂ€tzliche Tabellen verwenden.

Ferner werden diese Verfahren ausfĂŒhrlich beschrieben.


Eingebaute Geotypen


Bekanntlich unterstĂŒtzt SQL Server 2008 Geografie- und Geometriedatentypen, mit denen geografische (auf der Kugel) und geometrische (auf der Ebene) Informationen wie Punkte, Linien, Polygone usw. angegeben werden können. . Um alle Fotos abzurufen, die von einem Rechteck mit den Koordinaten ( lngMin latMin ) und ( latMax lngMax ) eingeschlossen sind, können Sie die folgende Abfrage verwenden:


 DECLARE @h geography; DECLARE @p geography; SET @rect = geography::STGeomFromText('POLYGON((lngMin latMin, lngMax latMin, lngMax latMax, lngMin latMax, lngMin latMin))', 4326); SELECT TOP @cound id, image75Path, geoTag.Lat as Lat, geoTag.Long as Lng, popularity, width, height FROM Photo WITH (INDEX(IX_Photo_geoTag)) WHERE @rect.STContains(geoTag) = 1 ORDER BY popularity DESC 

Beachten Sie, dass das Polygon gegen den Uhrzeigersinn ausgerichtet ist und der durch die Koordinaten definierte rĂ€umliche Index IX_Photo_geoTag verwendet wird (außerdem werden rĂ€umliche Indizes mithilfe von B-BĂ€umen erstellt ).


Es stellte sich jedoch heraus, dass rÀumliche Indizes in Microsoft SQL Server 2008 nicht funktionieren, wenn die Spalte mit Geotypen NULL Werte akzeptieren kann und ein zusammengesetzter Index keine Spalte mit dem Geografiedatentyp enthalten kann. Diese Frage wurde in Stackoverflow erörtert . Aus diesem Grund wird die Leistung solcher Abfragen (ohne Indizes) sehr gering.


Die folgenden AnsÀtze können dieses Problem lösen:


  • Da NULL Werte nicht verwendet werden können, sind die Standardwerte fĂŒr diese Spalte Koordinaten (0, 0), die auf einen Ort im Atlantik in der NĂ€he von Afrika verweisen (Ausgangspunkt fĂŒr die Messung von LĂ€ngen- und Breitengraden). An diesem Ort und in der NĂ€he können sich jedoch die realen Punkte befinden, und die Fotos, die nicht von der Karte stammen, sollten ignoriert werden. Wenn Sie den Nullpunkt (0, 0) in den Ă€ußersten Nordpunkt (0, 90) Ă€ndern, ist alles viel besser, da der 90. Breitengrad auf den Rand der Karte zeigt und Sie diesen Wert beim Erstellen des Gitters ignorieren sollten (d. H. bis zum Breitengrad 89 aufbauen).
  • Verwenden von SQL Server 2012 oder höher und Ändern der KompatibilitĂ€tsstufe der Datenbank auf 110 oder höher durch AusfĂŒhren von ALTER DATABASE database_name SET COMPATIBILITY_LEVEL = 110 . In dieser Version von SQL Server wurde der Fehler mit NULL Werten von Geotypen behoben und die UnterstĂŒtzung von Polygonen mit unterschiedlichen Ausrichtungen (gegen den Uhrzeigersinn und im Uhrzeigersinn) hinzugefĂŒgt.

Trotz der großen Möglichkeiten von Geotypen (mit denen Sie nicht nur eine einfache Auswahl wie oben gezeigt treffen können, sondern auch Entfernungen und verschiedene Polygone verwenden können) haben wir sie in unserem Projekt nicht verwendet.


Normale Auswahl


Verwenden Sie die folgende Abfrage, um Fotos aus dem durch Koordinaten ( lngMin latMin ) und ( latMax lngMax ) begrenzten Bereich auszuwÀhlen:


 SELECT TOP @Count id, url, ... FROM Photo WHERE latitude > @latMin AND longitude > @lngMin AND latitude < @latMax AND longitude < @lngMax ORDER BY popularity DESC 

Beachten Sie, dass Sie in diesem Fall (im Gegensatz zur ersten Methode) beliebige Indizes fĂŒr latitude und longitude erstellen können, da ein gewöhnlicher Float-Datentyp verwendet wird. Diese Auswahl enthĂ€lt jedoch 4 Vergleichsoperationen.


ZusÀtzliche Hash-Tabelle verwenden


Die optimalste Lösung fĂŒr das Problem der Auswahl von Fotos aus bestimmten Bereichen besteht darin, zusĂ€tzliche Tabellen- Zooms zu erstellen, in denen Zeichenfolgen gespeichert sind, die fĂŒr jeden Zoom Hashes von Bereichen enthalten, wie unten gezeigt.



Die folgende SQL-Abfrage kann verwendet werden ( zn - aktuelle zn ):


 DECLARE @hash float; SET @hash = (@latMin + 90) + (@lngMin + 180) * 180 + (@latMax + 90) * 64800 + (@lngMax + 180) * 11664000; SELECT TOP @Count id, url, ... FROM Photo WHERE id = (SELECT id FROM Zooms WHERE zn = @hash) 

Der Nachteil dieses Ansatzes besteht darin, dass die zusÀtzliche Tabelle zusÀtzlichen Speicherplatz belegt.


Trotz der Vorteile der letzteren Methode haben wir die zweite Methode ( normale Auswahl ) auf dem Server verwendet, da sie eine gute Leistung zeigte.


Zwischenspeichern von Fotos fĂŒr den Multithread-Zugriff


Nach dem Extrahieren der Informationen aus der Datenbank auf die eine oder andere Weise werden Fotos mithilfe eines Synchronisierungsobjekts in den Server-Cache gestellt, um Multithreading wie folgt zu unterstĂŒtzen:


 private static object SyncObject = new object(); ... List<Photo> photos = (List<Photo>)CachedAreas[hash]; if (photos == null) { // Use lock to avoid extracting from and adding to the cache more than once. lock (SyncObject) { photos = (List<Photo>)CachedAreas[hash]; if (photos == null) { photos = PhotoList.GetAllFromRect(latMin, lngMin, latMax, lngMax, count); // Adding information about photos to the cache with a storage time of 2 minutes with a high storage priority. CachedAreas.Add(hash, photos, null, DateTime.Now.AddSeconds(120), Cache.NoSlidingExpiration, CacheItemPriority.High, null); } } } // Further usage of CachedAreas[hash] 

In diesem Abschnitt wurden die Serverfunktionen zum Abrufen und Speichern von Fotos aus der Datenbank beschrieben. Im nÀchsten Abschnitt wird beschrieben, was auf der Clientseite im Browser geschieht.


Client-Seite


Zur Visualisierung der Karte und der Fotos wurde die Google Maps-API verwendet. ZunÀchst muss die Benutzerkarte an einen bestimmten Ort verschoben werden, der der geografischen Position der Fotos entspricht.


Karte initialisieren


Es gibt zwei Möglichkeiten, die Geolokalisierung beim Initialisieren der Karte zu bestimmen: Verwenden Sie die Funktionen von HTML5 oder verwenden Sie vorberechnete Koordinaten fĂŒr Regionen.


Bestimmen der Geolokalisierung mit HTML5


 function detectRegion() { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(success); } else { map.setZoom(defaultZoom); map.setCenter(defaultPoint); } } function success(position) { ... map.setZoom(defaultZoom); map.setCenter(new google.maps.LatLng(position.coords.latitude, position.coords.longitude)); } 

Der Nachteil dieses Ansatzes besteht darin, dass nicht alle Browser diese FunktionalitĂ€t von HTML5 unterstĂŒtzen und der Benutzer möglicherweise keinen Zugriff auf Geoinformationen auf seinem GerĂ€t zulĂ€sst.


Ermitteln der Geolokalisierung anhand von Informationen vom Server


Die Karte wird im folgenden Abschnitt des Quellcodes initialisiert, wobei bounds die Koordinaten der vom Server zurĂŒckgegebenen Region (besiedeltes Gebiet, Region oder Land) sind. Die ungefĂ€hre getZoomFromBounds wird in der Funktion getZoomFromBounds (aus dem Stackoverflow entnommen ) berechnet .


 var northEast = bounds.getNorthEast(); var southWest = bounds.getSouthWest(); var myOptions = { zoom: getZoomFromBounds(northEast, southWest), center: new google.maps.LatLng((northEast.lat() + southWest.lat()) / 2, (northEast.lng() + southWest.lng()) / 2), mapTypeId: google.maps.MapTypeId.ROADMAP, minZoom: 3, maxZoom: 19 } map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); 

 function getZoomFromBounds(ne, sw) { var GLOBE_WIDTH = 256; // a constant in Google's map projection var west = sw.lng(); var east = ne.lng(); var angle = east - west; if (angle < 0) { angle += 360; } return Math.round(Math.log($('#map_canvas').width() * 360 / angle / GLOBE_WIDTH) / Math.LN2); } 

Auf dem Server werden die Regionen abhĂ€ngig von der IP-Adresse des Benutzers berechnet. Um alle Koordinaten der Grenzen fĂŒr jede Region zu aggregieren, wurde die Google-Geokodierungs-API verwendet, obwohl es nicht legitim ist, solche Informationen offline zu verwenden. DarĂŒber hinaus gibt es ein Limit von 2500 Anfragen pro Tag. FĂŒr jede Stadt, Region und jedes Land aus unserer Datenbank wurde eine Abfrage generiert, die die erforderlichen Grenzen des viewport und der bounds zurĂŒckgab. Sie unterscheiden sich nur fĂŒr große Bereiche, die nicht vollstĂ€ndig in das Ansichtsfenster passen. Wenn der Server einen Fehler zurĂŒckgegeben hat, wurden andere Abfragen verwendet, in denen die Muttersprache dieser Region oder Englisch verwendet wurde, der Teil {BestĂŒckter Bereich} entfernt wurde usw. http://maps.googleapis.com/maps/api/geocode/xml?address={Country},{Region},{Populated area}&sensor=false


Beispiel fĂŒr die folgende Abfrage: http://maps.googleapis.com/maps/api/geocode/xml?address=Russia, Ivanovo% 20 area, Ivanovo & sensor = false


Die folgenden Koordinaten werden zurĂŒckgegeben (Fragment)
 ... <location> <lat>56.9951313</lat> <lng>40.9796047</lng> </location> <location_type>APPROXIMATE</location_type> <viewport> <southwest> <lat>56.9420231</lat> <lng>40.8765941</lng> </southwest> <northeast> <lat>57.0703221</lat> <lng>41.0876169</lng> </northeast> </viewport> <bounds> <southwest> <lat>56.9420231</lat> <lng>40.8765941</lng> </southwest> <northeast> <lat>57.0703221</lat> <lng>41.0876169</lng> </northeast> </bounds> ... 

Berechnung teilweise sichtbarer rechteckiger FlÀchen


Berechnung der GrĂ¶ĂŸe von Caching-Bereichen


Wie bereits erwĂ€hnt, werden alle Fotos sowohl auf der Client- als auch auf der Serverseite von rechteckigen Bereichen zwischengespeichert, deren Startpunkt ein beliebiger Punkt ist (in unserem Fall der Punkt mit den Koordinaten (0, 0)), und die GrĂ¶ĂŸe wird abhĂ€ngig berechnet auf der aktuellen Zoomstufe wie folgt:


 // The initial window at which initMapSizeLat and initMapSizeLng were calculated var initDefaultDimX = 1000, var initDefaultDimY = 800; // The current default viewport which depends on the size of the areas. var currentDefaultDimX = 1080, var currentDefaultDimY = 500; var initMapSizeLat = 0.0003019; var initMapSizeLng = 0.00067055; // The coefficient of size reduction (increase). var initRatio = 0.75; // To calculate the size of the smallest caching area, the map was zoomed in to the maximum zoom level // Ie initMapSizeLat and initMapSizeLng were calculated empirically. var initZoomSize = new google.maps.Size( initMapSizeLat / initDefaultDimX * currentDefaultDimX * initRatio, initMapSizeLng / initDefaultDimY * currentDefaultDimY * initRatio); // All subsequent sizes of areas can be calculated based only on the smallest area (by multiplying each size by 2, because with increasing the zoom level by 1, the linear dimensions increase by 2 times, and the quadratic dimensions increase by 4 times). function initZoomSizes() { zoomSizes = []; var coef = 1; for (var i = 21; i >= 0; i--) { zoomSizes[i] = new google.maps.Size(initZoomSize.width * coef, initZoomSize.height * coef); coef *= 2; } } 

Somit betrĂ€gt die GrĂ¶ĂŸe des rechteckigen Bereichs bei jeder 0.75^2=0.5625 gegenĂŒber der GrĂ¶ĂŸe des aktuellen Ansichtsfensters, wenn er eine Breite von 1080 Pixel und eine Höhe von 500 Pixel hat.


Verwenden der Verzögerung beim Neuzeichnen


Da das Neuzeichnen aller Fotos auf der Karte kein schneller Vorgang ist (wie spÀter gezeigt wird), haben wir uns entschlossen, dies nach der Benutzereingabe mit einiger Verzögerung zu tun:


 google.maps.event.addListener(map, 'bounds_changed', function () { if (boundsChangedInverval != undefined) clearInterval(boundsChangedInverval); var zoom = map.getZoom(); boundsChangedInverval = setTimeout(function () { boundsChanged(); }, prevZoom === zoom ? moveUpdateDelay : zoomUpdateDelay); prevZoom = zoom; }); 

Berechnung von Koordinaten und Hashes von teilweise sichtbaren Bereichen


Die Berechnung der Koordinaten und Hashes aller Rechtecke, die das sichtbare Fenster mit Koordinaten ( latMin , lngMin ) und Dimensionen ĂŒberlappen, die mit dem zuvor beschriebenen Algorithmus berechnet wurden, erfolgt wie folgt:



 var s = zoomSizes[zoom]; var beginLat = Math.floor((latMin - initPoint.x) / s.width) * s.width + initPoint.x; var beginLng = Math.floor((lngMin - initPoint.y) / s.height) * s.height + initPoint.y; var lat = beginLat; var lng = beginLng; if (lngMax <= beginLng) beginLng = beginLng - 360; while (lat <= maxlat) { lng = beginLng; while (lng <= maxLng) { // lat and normalizeLng(lng) coordinates are the coordinates of the overlapping rectangles. // Longitude normalization is used because the right boundary can be greater than 180 or the left boundary can be less than -180. loadIfNeeded(lat, normalizeLng(lng)); lng += s.height; } lat += s.width; } function normalizeLng(lng) { var rtn = lng % 360; if (rtn <= 0) rtn += 360; if (rtn > 180) rtn -= 360; return rtn; } 

Danach wird fĂŒr jeden Bereich die folgende Funktion aufgerufen, die bei Bedarf die Anfrage an den Server sendet. Die Formel der Hash-Berechnung gibt fĂŒr jeden Bereich einen eindeutigen Wert zurĂŒck, da der Startpunkt und die Abmessungen festgelegt sind.


 function loadIfNeeded(lat, lng) { var hash = calculateHash(lat, lng, zoom); if (!(hash in items)) { // Send a query to the database and put this cell in the client cache. } else { // Do nothing. } } function calculateHash(lat, lng, zoom) { // lat: [-90..90] // lng: [-180..180] return (lat + 90) + ((lng + 180) * 180) + (zoom * 64800); } 

Neuzeichnen der angezeigten Fotos


Nachdem alle Fotos heruntergeladen oder aus dem Cache extrahiert wurden, mĂŒssen einige neu gezeichnet werden. Bei einer großen Anzahl von Fotos oder Markierungen an einem Ort sollten einige davon ausgeblendet sein, aber dann wird unklar, wie viele Fotos sich an diesem Ort befinden. Um dieses Problem zu lösen, haben wir uns entschieden, zwei Arten von Markierungen zu unterstĂŒtzen: Markierungen, die Fotos anzeigen, und Markierungen, die anzeigen, dass sich an dieser Stelle Fotos befinden. Wenn außerdem alle Markierungen ausgeblendet werden, wenn die Grenzen geĂ€ndert und dann erneut angezeigt werden, kann der Benutzer ein Flackern bemerken. Um diese Probleme zu lösen, wurde der folgende Algorithmus entwickelt:


  1. Extrahieren aller sichtbaren Fotos aus dem Client-Cache in das Array visMarks . Die Berechnung dieser FlÀchen mit Fotos wurde oben beschrieben.
  2. Sortieren der empfangenen Marker nach Beliebtheit.
  3. Suchen ĂŒberlappender Marker mithilfe von markerSize , SmallMarkerSize , minPhotoDistRatio und pixelDistance .
  4. Erstellen von Arrays mit großen Markern mit maxBigVisPhotosCount und kleinen Markern mit maxSmlVisPhotosCount .
  5. Definieren Sie alte Marker, die ausgeblendet werden sollen, und fĂŒgen Sie sie mithilfe von refreshMarkerArrays zu smlMarksToHide und bigMarksToHide refreshMarkerArrays .
  6. Aktualisieren der Sichtbarkeit und des zIndex fĂŒr neue Markierungen, die mit updateMarkersVis angezeigt werden updateMarkersVis .
  7. HinzufĂŒgen von Fotos, die zum aktuellen Zeitpunkt sichtbar wurden, zum Feed mit addPhotoToRibbon .

Algorithmus zur Neuberechnung sichtbarer Marker
 function redraw() { isRedrawing = true; var visMarker; var visMarks = []; var visBigMarks2; var visSmlMarks2; var bigMarksToHide = []; var smlMarksToHide = []; var photo; var i, j; var bounds = map.getBounds(); var northEast = bounds.getNorthEast(); var southWest = bounds.getSouthWest(); var latMin = southWest.lat(); var lngMin = southWest.lng(); var latMax = northEast.lat(); var lngMax = northEast.lng(); var ratio = (latMax - latMin) / $("#map_canvas").height(); var zoom = map.getZoom(); visMarks = []; var k = 0; var s = zoomSizes[zoom]; var beginLat = Math.floor((latMin - initPoint.x) / s.width) * s.width + initPoint.x; var beginLng = Math.floor((lngMin - initPoint.y) / s.height) * s.height + initPoint.y; var lat = beginLat; var lng = beginLng; i = 0; if (lngMax <= beginLng) beginLng = beginLng - 360; // Extracting all visible markers. while (lat <= latMax) { lng = beginLng; while (lng <= lngMax) { var hash = calcHash(lat, normLng(lng), zoom); if (!(hash in curItems)) { } else { var item = curItems[hash]; for (photo in item.photos) { if (bounds.contains(item.photos[photo].latLng)) { visMarks[i] = item.photos[photo]; visMarks[i].overlapCount = 0; i++; } } } k++; lng += s.height; } lat += s.width; } // Sorting markers by popularity. visMarks.sort(function (a, b) { if (b.priority !== a.priority) { return b.priority - a.priority; } else if (b.popularity !== a.popularity) { return b.popularity - a.popularity; } else { return b.id - a.id; } }); // Finding overlapping markers and markers that exceed a certain specified number. var curInd; var contains; var contains2; var dist; visBigMarks2 = []; visSmlMarks2 = []; for (i = 0; i < visMarks.length; i++) { contains = false; contains2 = false; visMarker = visMarks[i]; for (j = 0; j < visBigMarks2.length; j++) { dist = pixelDistance(visMarker.latLng, visBigMarks2[j].latLng, zoom); if (dist <= markerSize * minPhotoDistRatio) { contains = true; if (contains && contains2) break; } if (dist <= (markerSize + smallMarkerSize) / 2) { contains2 = true; if (contains && contains2) break; } } if (!contains) { if (visBigMarks2.length < maxBigVisPhotosCount) { smlMarksToHide[smlMarksToHide.length] = visMarker; visBigMarks2[visBigMarks2.length] = visMarker; } } else { bigMarksToHide[bigMarksToHide.length] = visMarker; if (!contains2 && visSmlMarks2.length < maxSmlVisPhotosCount) { visSmlMarks2[visSmlMarks2.length] = visMarker; } else { visBigMarks2[j].overlapCount++; } } } // Adding markers that should be hidden to smlMarksToHide and bigMarksToHide. refreshMarkerArrays(visibleSmallMarkers, visSmlMarks2, smlMarksToHide); refreshMarkerArrays(visibleBigMarkers, visBigMarks2, bigMarksToHide); // Hiding invisible markers and displaying visible markers when zIndex changes. var curZInd = maxBigVisPhotosCount + 1; curZInd = updateMarkersVis(visBigMarks2, bigMarksToHide, true, curZInd); curZInd = 0; curZInd = updateMarkersVis(visSmlMarks2, smlMarksToHide, false, curZInd); visibleBigMarkers = visBigMarks2; visibleSmallMarkers = visSmlMarks2; // Adding visible photos to the feed. trPhotosOnMap.innerHTML = ''; for (var marker in visBigMarks2) { addPhotoToRibbon(visBigMarks2[marker]); } isRedrawing = false; } function refreshMarkerArrays(oldArr, newArr, toHide) { for (var j = 0; j < oldArr.length; j++) { contains = false; var visMarker = oldArr[j]; for (i = 0; i < newArr.length; i++) { if (newArr[i].id === visMarker.id) { contains = true; break; } } if (!contains) { toHide[toHide.length] = visMarker; } } } function updateMarkersVis(showArr, hideArr, big, curZInd) { var marker; var bounds = map.getBounds(); for (var i = 0; i < showArr.length; i++) { var photo = showArr[i]; if (big) { marker = photo.bigMarker; $('#divOvlpCount' + photo.id).html(photo.overlapCount); } else { marker = photo.smlMarker; } marker.setZIndex(++curZInd); if (marker.getMap() === null) { marker.setMap(map); } } for (i = 0; i < hideArr.length; i++) { marker = big ? hideArr[i].bigMarker : hideArr[i].smlMarker; if (marker.getMap() !== null) { marker.setMap(null); marker.setZIndex(0); if (!bounds.contains(hideArr[i].latLng)) hideArr[i].priority = 0; } } return curZInd; } function addPhotoToRibbon(marker) { var td = createColumn(marker); if (isLatLngValid(marker.latLng)) { trPhotosOnMap.appendChild(td); } else { trPhotosNotOnMap.appendChild(td); if (photoViewMode == 'user') { var img = $("#photo" + marker.id).children()[0]; $('#photo' + marker.id).draggable({ helper: 'clone', appendTo: $('#map_canvas'), stop: function (e) { var mapBoundingRect = document.getElementById("map_canvas").getBoundingClientRect(); var point = new google.maps.Point(e.pageX - mapBoundingRect.left, e.pageY - mapBoundingRect.top); var latLng = overlay.getProjection().fromContainerPixelToLatLng(point); marker.latLng = latLng; marker.priority = ++curPriority; placeMarker(marker); }, containment: 'parent', distance: 5 }); } } } 

Entfernung auf der Karte


Um den Abstand zwischen zwei Punkten auf der Karte in Pixel zu berechnen, wird die folgende Funktion verwendet:


 var Offset = 268435456; var Radius = 85445659.4471; function pixelDistance(latLng1, latLng2, zoom) { var x1 = lonToX(latLng1.lng()); var y1 = latToY(latLng1.lat()); var x2 = lonToX(latLng2.lng()); var y2 = latToY(latLng2.lat()); return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) >> (21 - zoom); } function lonToX(lng) { return Math.round(Offset + Radius * lng * Math.PI / 180); } function latToY(lat) { return Math.round(Offset - Radius * Math.log((1 + Math.sin(lat * Math.PI / 180)) / (1 - Math.sin(lat * Math.PI / 180))) / 2); } 

Diese Funktion wurde auch beim Stackoverflow gefunden.


Um die Markierungen wie Kreise mit Fotos aussehen zu lassen (wie vkontakte), wurde das Plugin RichMarker verwendet und dem div-Element ein beliebiger Stil hinzugefĂŒgt .


Fazit


Es stellte sich heraus, dass wir, um Fotos schnell und korrekt auf der Karte anzuzeigen, interessante und nicht triviale Probleme im Zusammenhang mit Caching und sphĂ€rischer Geometrie lösen mussten. Trotz der Tatsache, dass nicht alle beschriebenen Methoden tatsĂ€chlich in unserem Projekt verwendet wurden, wurde keine Zeit verschwendet, da die Erfahrungen, die wir sammeln, in anderen Projekten nĂŒtzlich sein können und auch fĂŒr diejenigen nĂŒtzlich sein können, die diesen Artikel gelesen und verstanden haben.

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


All Articles