├── geoalbum.css ├── README.md ├── Leaflet.LetterMarker.js └── geoalbum.js /geoalbum.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 10px; 3 | font-size: 10pt; 4 | font-family: "Verdana", "Arial", "Helvetica", sans-serif; 5 | } 6 | 7 | h1 { 8 | font-size: 18pt; 9 | font-weight: normal; 10 | } 11 | 12 | img { 13 | border: 0; 14 | } 15 | 16 | #content { 17 | width: 650px; 18 | float: left; 19 | padding-bottom: 10px; 20 | } 21 | 22 | #maps { 23 | margin-left: 680px; 24 | } 25 | 26 | #detailmap { 27 | margin-top: 1em; 28 | height: 350px; 29 | } 30 | 31 | #overviewmap { 32 | height: 350px; 33 | } 34 | .photoidx { 35 | position: absolute; 36 | background-color: white; 37 | margin-left: 6px; 38 | margin-top: 6px; 39 | text-align: center; 40 | font-size: 10px; 41 | width: 15px; 42 | height: 15px; 43 | color: white; 44 | background-color: black; 45 | border-radius: 7px; 46 | opacity: 0.4; 47 | } 48 | 49 | .nav a { 50 | font-size: 14pt; 51 | color: darkblue; 52 | text-decoration: none; 53 | background: #eef; 54 | padding: 5px 15px; 55 | border-radius: 10px; 56 | } 57 | 58 | .navright { 59 | float: right; 60 | } 61 | 62 | .navleft { 63 | float: left; 64 | } 65 | 66 | .nav-bottom { 67 | margin: 1em 0; 68 | } 69 | 70 | .div-p { 71 | margin: 1em 0; 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeoAlbum 2 | 3 | So. You've got a hundred photos and a GPS trace, and you want to make browsing those photos comfortable for your readers. 4 | Here is the solution. Make thumbnails for all photos and make an html file like this: 5 | 6 | ```html 7 | Georeferenced photos 8 | 9 | 10 | 11 | 12 | 13 |

Opening slide

14 |

Hello and welcome to my photo album!

15 |

Change pages by pressing arrow buttons on keyboard, or by clicking relevant links on top and bottom. 16 | The map to the right shows page locations, you can click on numbers to open a page for that location. 17 | All images can be opened in bigger size by clicking on them.

18 |
19 | 20 |

The first photo

21 |
22 |

This is first two photos I took on my trip.

23 |
24 |
25 | 26 |
...
27 | ``` 28 | 29 | Note `geoalbum.js` and `leaflet.js` included. Those will make a beautiful photo album with a map and slides from this 30 | simple page. You can check out an [example album](http://textual.ru/lk130518/), for which this library was 31 | written. Thanks to Tom MacWright for inspiration. The source is released under WTFPL, so feel free to alter 32 | it in any way. 33 | 34 | # Leaflet.LetterMarker 35 | 36 | The album needed a special kind of marker: the one with letters on it. So I wrote a plugin that slightly 37 | modifies leaflet's Marker class. Just include `Leaflet.LetterMarker.js` script in your page and create 38 | markers like that: 39 | 40 | L.letterMarker([12.34, 56.78], 'A', { color: 'blue' }).addTo(map); 41 | 42 | This plugin is also licensed WTFPL. 43 | 44 | -------------------------------------------------------------------------------- /Leaflet.LetterMarker.js: -------------------------------------------------------------------------------- 1 | // Letter marker plugin for Leaflet. Written by Ilya Zverev. Licensed WTFPL. 2 | 3 | L.LetterMarker = L.Marker.extend({ 4 | options: { 5 | letter: 'A', 6 | color: 'black', 7 | riseOnHover: true, 8 | icon: new L.DivIcon({popupAnchor: [2, -2]}) 9 | }, 10 | 11 | initialize: function( latlng, letter, options ) { 12 | L.Marker.prototype.initialize.call(this, latlng, options); 13 | this.options.letter = letter; 14 | }, 15 | 16 | _initIcon: function() { 17 | var options = this.options, 18 | map = this._map, 19 | animation = (map.options.zoomAnimation && map.options.markerZoomAnimation), 20 | classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide'; 21 | 22 | if (!this._icon) { 23 | var div = document.createElement('div'); 24 | div.innerHTML = '' + options.letter + ''; 25 | div.className = 'leaflet-marker-icon'; 26 | div.style.marginLeft = '-7px'; 27 | div.style.marginTop = '-7px'; 28 | div.style.width = '15px'; 29 | div.style.height = '15px'; 30 | div.style.borderRadius = '7px'; 31 | div.style.fontSize = '10px'; 32 | div.style.fontFamily = 'sans-serif'; 33 | div.style.fontWeight = 'bold'; 34 | div.style.textAlign = 'center'; 35 | div.style.lineHeight = '15px'; 36 | div.style.cursor = options.clickable ? 'hand' : 'default'; 37 | div.style.color = 'white'; 38 | div.style.backgroundColor = options.color; 39 | this._icon = div; 40 | 41 | if (options.title) { 42 | this._icon.title = options.title; 43 | } 44 | 45 | this._initInteraction(); 46 | 47 | L.DomUtil.addClass(this._icon, classToAdd); 48 | 49 | if (options.riseOnHover) { 50 | L.DomEvent 51 | .on(this._icon, 'mouseover', this._bringToFront, this) 52 | .on(this._icon, 'mouseout', this._resetZIndex, this); 53 | } 54 | } 55 | 56 | var panes = this._map._panes; 57 | panes.markerPane.appendChild(this._icon); 58 | }, 59 | 60 | setColor: function( color ) { 61 | if( !this._icon ) 62 | this.options.color = color; 63 | else 64 | this._icon.style.backgroundColor = color; 65 | } 66 | }); 67 | 68 | L.letterMarker = function(latlng, letter, options) { 69 | return new L.LetterMarker(latlng, letter, options); 70 | }; 71 | -------------------------------------------------------------------------------- /geoalbum.js: -------------------------------------------------------------------------------- 1 | // GeoAlbum.js. Inspired by Tom MacWright's Big, partly based on Weenote, written by Ilya Zverev. Licensed WTFPL. 2 | 3 | var geoAlbumInitialized = false; 4 | window.onload = function() { initGeoAlbum(); } 5 | 6 | function initGeoAlbum() { 7 | if( geoAlbumInitialized ) return; 8 | geoAlbumInitialized = true; 9 | 10 | var prevText = '← Назад'; 11 | var nextText = 'Дальше →'; 12 | var detailZoom = 15; 13 | var overviewZoom = 10; 14 | 15 | var body = document.body; 16 | var pages = {}; 17 | var page; 18 | var content; 19 | var pageMarkersLayer = L.layerGroup(); 20 | var photoMarkers = {}; 21 | var currentDetailLayer; 22 | var overviewMap, detailMap; 23 | 24 | function process( page, idx ) { 25 | // find all coordinates, also mark photos 26 | var lat = 0.0, lon = 0.0; 27 | var photoIndices = 'ABCDEFGHIJKLMN'; 28 | var photoIdx = 0; 29 | var photoLayer = L.layerGroup(); 30 | var children = page.childNodes; 31 | for( var i = 0; i < children.length; i++ ) { 32 | var child = children[i]; 33 | if( child.nodeType != 1 ) continue; 34 | var images = child.getElementsByTagName('img'); 35 | if( images.length > 0 ) { 36 | child.className = 'div-p ' + child.className; 37 | child.style.overflowX = 'auto'; 38 | } 39 | if( child.hasAttribute('lat') && child.hasAttribute('lon') ) { 40 | var plat = +child.getAttribute('lat'); 41 | var plon = +child.getAttribute('lon'); 42 | lat += plat; lon += plon; 43 | var letter = photoIndices[photoIdx++]; 44 | photoLayer.addLayer(L.letterMarker([plat, plon], letter, {clickable: false})); 45 | var letterDiv = document.createElement('div'); 46 | letterDiv.className = 'photoidx'; 47 | letterDiv.appendChild(document.createTextNode(letter)); 48 | child.insertBefore(letterDiv, child.firstChild); 49 | } 50 | } 51 | if( photoIdx > 0 ) 52 | photoMarkers[idx] = photoLayer; 53 | 54 | if( page.hasAttribute('lat') && page.hasAttribute('lon') ) { 55 | lat = +page.getAttribute('lat'); 56 | lon = +page.getAttribute('lon'); 57 | } else if( photoIdx > 0 ) { 58 | lat /= photoIdx; 59 | lon /= photoIdx; 60 | } 61 | if( lat > 0 ) { 62 | pageMarkersLayer.addLayer(L.letterMarker([lat, lon], idx) 63 | .on('click', function() { window.location.hash = idx})); 64 | } 65 | 66 | // Add navigation links 67 | var nav = ''; 68 | if( idx > 1 ) nav += '' + prevText + ''; 69 | if( idx < count ) nav += '' + nextText + ''; 70 | nav += ''; 71 | page.innerHTML = '