In this article, I decided to describe how the functionality of selecting and displaying photos on a specific place on the map was implemented in our photo service gfranq.com. The photo service does not work now.   Since we had a lot of photos in our service and sending requests to database every time the viewport changes was too resource-intensive, it was logical to divide the map into several areas that contain information about the retrieved data. For obvious reasons, these areas have rectangular shape (although hexagonal grid was considered too). As the areas become more spherical at large scales, elements of spherical geometry and tools for it were also considered. In this article, the following issues were raised: The following methods of selecting and storing geoinformation in the database were designed: Further, these methods will be described in details. As is known, SQL Server 2008 supports geography and geometry data types, which allow to specify geographical (on the sphere) and geometrical (on the plane) information, such as points, lines, polygons etc.. In order to retrieve all photos enclosed by a rectangle with coordinates (lngMin latMin) and (latMax lngMax), you can use the following query: Note that the polygon is oriented counterclockwise and the spatial index IX_Photo_geoTag defined by the coordinates is used (besides, spatial indexes are built using B-trees). However, it turned out that in Microsoft SQL Server 2008, spatial indexes do not work if the column with geotypes can accept NULL values, and a composite index cannot contain a column with geography data type, and this question was discussed on Stackoverflow. That's why the performance of such queries (without indexes) becomes very low. The following approaches can solve this problem: Despite the wide possibilities of geotypes (they allow you to make not only a simple selection as shown above, but also to use distances and different polygons), we did not use them in our project. To select photos from the area bounded by coordinates (lngMin latMin) and (latMax lngMax), use the following query: Note that in this case you can create any indexes for latitude and longitude fields (in contrast to the first method), because an ordinary float data type is used. However, there are 4 comparison operations in this selection. The most optimal solution to the problem of selecting photos from certain areas is to create additional table Zooms which stores strings containing hashes of areas for each zoom, as shown below. The following SQL query can be used (zn — current zoom level): The disadvantage of this approach is that the additional table occupies additional memory space. Despite the advantages of the latter method, we used the second method (Normal selection) on the server, as it showed good performance. After extracting the information from the database in one way or another, photos are placed in the server cache using synchronizing object to support multithreading as follows: This section described server functionality for retrieving photos from the database and saving them to the cache. The next section will describe what happens on the client side in the browser. To visualize the map and photos on it, the Google Maps API was used. First, the user map must be moved to a certain place, corresponding to the geolocation of the photos. There are two ways to determine the geolocation when initializing the map: to use the capabilities of HTML5 or to use pre-calculated coordinates for regions. The disadvantage of this approach is that not all browsers support this functionality of HTML5 and the user may not allow access to geoinformation on his device. The map is initialized in the section of the source code below, where bounds is the coordinates of the region (populated area, region or country) returned by the server. The approximate zoom level is calculated in the function getZoomFromBounds (taken from stackoverflow). On the server, the regions are calculated depending on the user's IP address. To aggregate all coordinates of boundaries for each region, google geocoding api was used, although it is not legitimate to use such information offline; in addition, there is a limit of 2500 requests per day. For each city, region and country from our database, a query was generated that returned the required boundaries of viewport and bounds. They differ only for large areas that cannot fit completely into the viewport. If the server returned an error, then other queries were used in which the native language of that region or English was used, the part {Populated area} was removed, etc. http://maps.googleapis.com/maps/api/geocode/xml?address={Country},{Region},{Populated area}&sensor=false For example, for the following query: http://maps.googleapis.com/maps/api/geocode/xml?address=Россия, Ивановская%20область, Иваново&sensor=false So, as mentioned earlier, all photos on both client and server side are cached by rectangular areas, the starting point of which is an arbitrary point (in our case the point with coordinates (0, 0)), and the size is calculated depending on the current zoom level as follows: Thus, at each zoom level, the size of the rectangular area is 0.75^2=0.5625 from the size of the current viewport, if it has width of 1080px and height of 500px. Since the redrawing of all the photos on the map is not a fast operation (as will be shown later), we decided to do it with some delay after the user input: Calculation of coordinates and hashes of all the rectangles that overlap the visible window with coordinates (latMin, lngMin) and dimensions calculated using the algorithm described earlier is done as follows: After that, for each area, the following function is called, which sends the request to the server, if necessary. The formula of hash calculation returns a unique value for each area, because the starting point and dimensions are fixed. After all the photos are downloaded or extracted from the cache, some of them need to be redrawn. With a large number of photos, or markers, in one place, some of them should be hidden, but then it becomes unclear how many photos are located in this place. To solve this problem, we decided to support two types of markers: markers displaying photos and markers showing that there are photos in this place. In addition, if all markers are hidden when the boundaries are changed, and then they are redisplayed, then the user may notice flickering. To solve these problems, the following algorithm was developed: To calculate the distance between two points on the map in pixels the following function is used: This function was also found on stackoverflow. To make the markers look like circles with photos (like vkontakte), the plugin RichMarker was used with the addition of an arbitrary style to div element. It turned out that in order to display photos on the map quickly and correctly, we needed to solve quite interesting and non-trivial problems related to caching and spherical geometry. Despite the fact that not all described methods were actually used in our project, time was not wasted, as the experience we get might be useful in other projects, and it might also be useful for those who have read and understood this article. Software Developer at JetBrains (Kotlin Compiler)