📜 ⬆️ ⬇️

Maps in the browser without a network

Introduction


In my free time I am writing an application for finding ATMs in Minsk. And somehow going on vacation, I was left without the Internet on the phone. Everything would be fine, but I needed to find an ATM, withdraw money and not be late for the train. I opened my application and was very disappointed that I could not use the map offline. Of course, without connecting to the network in our time, it’s better not to go out of your home, but still the Internet on your favorite mobile device may be out at the wrong time.

Looking at the other applications on my phone, I noticed that, at best, they cache parts of the map that were loaded before. This could partly help me, but did not solve the problem completely. After that, I wondered if it was worth having the opportunity to view the map offline. Since my application is not native, but based on phonegap, those are browser based, then the story will be about how you can cache the map for browser applications in particular using google map api v3.

Idea


Somehow after all this, I remembered that google map api allows you to make your map implementation (for example, as I advise OSM ). Immediately I got the idea to slip an implementation that will always be available, and this can be done either by downloading maps to the cache if there is a connection, or by supplying the cache cards with the application.

At first I was thinking of using application cache, but I refused this idea, since its api does not provide wide possibilities for managing the loading of the cache.
')
In the end, I decided to just deliver the cache with the application.

I also had the idea to store sprites in localStorage, but this implementation has major flaws:
  1. localStorage size limit;
  2. data must be stored in base64, which is about 30% larger than the actual size.

The idea of ​​storing sprites in indexedDB or webSQL had to be abandoned due to the lack of synchronous api implementations.

To be or not to be


The first question I asked myself was it worth caching a card at all? That is, whether a specific city map will be used and whether detailed detailing is necessary. In my case, it was enough to have a cache of Minsk for a small zoom (10-15).

The second question: how much space will the cache take? If the average size of the sprite is 20 kb, then theoretically for a zoom of 10 (Minsk fully accommodates) you need 1 sprite (20 kb), for 11 - 4 (100 kb), for 12 - 16 (420 kb), for 13 - 64 (1.7 mb), for 14 - 256 (6.8 mb), 15 - 1024 (27 mb). A cache with a zoom of 14 seemed sufficient.

Download


I decided to take a real map and real sprites to find out how much space the cache actually takes. To do this, it was necessary to solve several school problems: create a polygon with a minimum perimeter from a set of points, translate the polar coordinates into the coordinates of the sprites of the map and find the sprites located in the polygon . After the script was ready I downloaded the sprites and got the following results (in brackets the total space occupied for this zoom):
ZoomTheoretical number of spritesTheoretical sprites sizeThe actual number of spritesReal size sprites
9one20 kb (20 kb)252 kb (52 kb)
tenone20 kb (40 kb)372 kb (124 kb)
elevenfour80 kb (100 kb)7204 kb (328 kb)
12sixteen320 kb (420 kb)17348 kb (676 kb)
13641.3 mb (1.7 mb)48820 kb (1.5 mb)
142565.1 mb (6.8 mb)1582.2 mb (3.7 mb)
15102420.5 mb (27 mb)5865.5 mb (9.3 mb)
sixteen409682 mb (109 mb)226415 mb (24.3 mb)

Sprites were downloaded if they were inside the Minsk ring road or if the objects I needed were on these sprites. Thus it turned out to significantly reduce the space occupied by sprites.

Since I had sprites, it remained to make the map work offline.

Without network


In order for the map to work with cached sprites, you need to tell it where to get the data, you can do it simply:
  1. map. mapTypes . set ( "LocalGmap" , new google. maps . ImageMapType ( {
  2. getTileUrl : function ( coord , zoom ) {
  3. return "cache /" + zoom + "/" + coord. x + "_" + coord. y + ".png"
  4. } ,
  5. tileSize : new google. maps . Size ( 256 , 256 ) ,
  6. name : "LocalGmap" ,
  7. maxZoom : 15
  8. } ) ) ;

The getTileUrl function returns a value that is inserted into the src attribute of the image, therefore, if we have a base64 view of images stored in localStorage, we can implement the map cache like this:
  1. map. mapTypes . set ( "WebStorageGmap" , new google. maps . ImageMapType ( {
  2. getTileUrl : function ( coord , zoom ) {
  3. return localStorage. getItem ( [ zoom , coord. x , coord. y ] . join ( '_' ) ) ;
  4. } ,
  5. tileSize : new google. maps . Size ( 256 , 256 ) ,
  6. name : "WebStorageGmap" ,
  7. maxZoom : 15
  8. } ) ) ;

But for now we are still tied to scripts, pictures and cursors google maps api.

Let's start with the most important script: http://maps.googleapis.com/maps/api/js?sensor=false . Download and replace the script with its local version, which we call gmapapi.js. This script mentions many references to some data.

Run again and see which scripts are loaded. This is http://maps.gstatic.com/cat_js/intl/en_us/mapfiles/api-3/8/5/main.js and many more scripts similar to http://maps.gstatic.com/cat_js/intl/ en_us / mapfiles / api-3/8/5 / {map, marker} .js .

The first script contains the kernel, the rest - additional components. Downloading main.js and since there is no mention of additional components in gmapapi.js, then quickly looking at main.js we get all the components of interest that we download in components.js:
google. maps .__ gjsload__ ( 'common' , ...
google. maps .__ gjsload__ ( 'controls' , ...
google. maps .__ gjsload__ ( 'directions' , ...
google. maps .__ gjsload__ ( 'distance_matrix' , ...
google. maps .__ gjsload__ ( 'drawing_impl' , ...
google. maps .__ gjsload__ ( 'elevation' , ...
google. maps .__ gjsload__ ( 'geocoder' , ...
google. maps .__ gjsload__ ( 'geometry' , ...
google. maps .__ gjsload__ ( 'infowindow' , ...
google. maps .__ gjsload__ ( 'kml' , ...
google. maps .__ gjsload__ ( 'layers' , ...
google. maps .__ gjsload__ ( 'map' , ...
google. maps .__ gjsload__ ( 'marker' , ...
google. maps .__ gjsload__ ( 'maxzoom' , ...
google. maps .__ gjsload__ ( 'onion' , ...
google. maps .__ gjsload__ ( 'overlay' , ...
google. maps .__ gjsload__ ( 'places_impl' , ...
google. maps .__ gjsload__ ( 'poly' , ...
google. maps .__ gjsload__ ( 'search_impl' , ...
google. maps .__ gjsload__ ( 'stats' , ...
google. maps .__ gjsload__ ( 'streetview' , ...
google. maps .__ gjsload__ ( 'usage' , ...
google. maps .__ gjsload__ ( 'util' , ...

Now, in gmapapi.js, we replace the load of main.js of an external file with a local file, and also we will load components.js, in order not to load the necessary components after initializing the map.

Look further: some scripts are loaded from http://maps.googleapis.com , which make incomprehensible magic:

We look for where they are mentioned, and this, as it turned out, is gmapapi.js. We substitute links to these scripts with local ones (for the fallback in the application cache to work). Add an empty.js script that will do nothing and add a bunch of “magic_ script empty.js” to the fallback section of our file manifest.
FALLBACK:
gmapcache / googleapis / maps / api / js / AuthenticationService.Authenticate gmapcache / empty.js
gmapcache / googleapis / maps / api / js / QuotaService.RecordEvent gmapcache / empty.js
gmapcache / googleapis / maps / api / js / StaticMapService.GetMapImage gmapcache / empty.js
gmapcache / googleapis / maps / api / js / ViewportInfoService.GetViewportInfo gmapcache / empty.js
gmapcache / googleapis / maps / gen_204 gmapcache / empty.js
gmapcache / googleapis / mapslt / ft gmapcache / empty.js

With all the scripts, now the pictures.

All images that are not sprites are loaded from http://maps.gstatic.com/mapfiles/ . A quick search across all files says that the string is mentioned in one place in gmapapi.js. We swing locally all pictures, we replace the found link with the local one.

Now for full work offline we push everything into the manifest file.
CACHE MANIFEST

NETWORK:
*

CACHE:
index.html
style.css

script.js
gmapapi.js
gmapcache / main.js
gmapcache / components.js
gmapcache / empty.js

gmapcache / gstatic / arrow-down.png
gmapcache / gstatic / cb / mod_cb_scout / cb_scout_sprite_api_003.png
gmapcache / gstatic / cb / target_locking.gif
gmapcache / gstatic / google_white.png
gmapcache / gstatic / iw3.png
gmapcache / gstatic / iws3.png
gmapcache / gstatic / mapcontrols3d7.png
gmapcache / gstatic / markers2 / marker_sprite.png
gmapcache / gstatic / mv / imgs8.png
gmapcache / gstatic / rotate2.png
gmapcache / gstatic / szc4.png
gmapcache / gstatic / transparent.png

gmapcache / gstatic / openhand_8_8.cur
gmapcache / gstatic / closedhand_8_8.cur

Everything, fully working offline map is ready!

Here are all the changes in gmapapi.js:
It wasIt became
[
" http://mt0.googleapis.com/mapslt/ft?hl=en-USu0026 " ,
" http://mt1.googleapis.com/mapslt/ft?hl=en-USu0026 "
]
[
"gmapcache / googleapis / mapslt / ft? hl = en-USu0026"
]
[
"en-US" , "US" , null , 0 , null , null ,
" http://maps.gstatic.com/mapfiles/ " ,
" http://csi.gstatic.com " ,
" https://maps.googleapis.com " ,
" http://maps.googleapis.com "
]
[
"en-US" , "US" , null , 0 , null , null ,
"gmapcache / gstatic /" ,
" http://csi.gstatic.com " ,
" https://maps.googleapis.com " ,
"gmapcache / googleapis"
]
getScript ( " http://maps.gstatic.com/intl/en_us/mapfiles/api-3/8/5/main.js " ) ;
getScript ( "gmapcache / main.js" ) ;
getScript ( "gmapcache / components.js" ) ;

In fact, gmapapi.js stores all settings for maps and there you can also specify links to sprites.

What's next?


Actually that's all.

So what I did:
  1. downloaded sprites locally;
  2. downloaded all the resources used locally (scripts, pictures, cursors);
  3. replaced the mention of external resources with local ones and added files to the cache section of the file manifest;
  4. made it so that the necessary scripts are not loaded after initialization;
  5. Replaced unnecessary files with dummies and added a file manifest section to the fallback.

This project is on github https://github.com/tbicr/OfflineMap , it consists of a parser and a site . Lazy can see the map here http://offline-map.appspot.com .

To see the work offline in the browser right away, go to http://offline-map.appspot.com , click “Prepare Web Storage”, wait a few seconds for the sprites to load, then turn off the Internet, click on WebStorageGmap and enjoy.

The “Prepare Web Storage” button uploads sprites to localStorage, and “Clear Web Storage” respectively clears it.

Card types:

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


All Articles