Map view for albums in galleria theme

This feature hacks galleria to show an openlayers map instead of the
picture. The pictures are represented by dots on the maps and clicking
them or the galleria thumbnails update the other (and the selected
picture).

By default, the map isn't shown: it needs to be enabled by the show_map
options.
This commit is contained in:
Cédric Bosdonnat
2013-11-26 10:33:56 +01:00
parent 2d27aabca9
commit 8e4a08ae18
13 changed files with 675 additions and 0 deletions

View File

@@ -176,6 +176,9 @@ class Image(Media):
def thumb_size(self):
return get_size(self.thumb_path)
def has_location(self):
return self.exif is not None and 'gps' in self.exif
class Video(Media):
"""Gather all informations on a video file."""
@@ -434,6 +437,13 @@ class Album(UnicodeMixin):
breadcrumb.reverse()
return breadcrumb
@property
def show_map(self):
"""Check if we have at least one photo with GPS location in the album
"""
return any(image.has_location() for image in self.images)
@property
def zip(self):
"""Make a ZIP archive with all media files and return its path.

View File

@@ -57,6 +57,7 @@ _DEFAULT_CONFIG = {
'piwik': {'tracker_url': '', 'site_id': 0},
'plugin_paths': [],
'plugins': [],
'show_map': False,
'source': '',
'theme': 'colorbox',
'thumb_dir': 'thumbnails',

View File

@@ -41,6 +41,10 @@ theme = 'galleria'
# Size of resized image (default: (640, 480))
img_size = (800, 600)
# Should we use openlayers to show a map of the images where possible?
# This option only has an effect on the galleria theme for the while.
# show_map = False
# Pilkit processor used to resize the image
# (see http://pilkit.readthedocs.org/en/latest/#processors)
# - ResizeToFit: fit the image within the specified dimensions (default)

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

View File

@@ -0,0 +1,478 @@
/* required styles */
.leaflet-map-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-pane,
.leaflet-tile-container,
.leaflet-overlay-pane,
.leaflet-shadow-pane,
.leaflet-marker-pane,
.leaflet-popup-pane,
.leaflet-overlay-pane svg,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
-ms-touch-action: none;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container img {
max-width: none !important;
}
/* stupid Android 2 doesn't understand "max-width: none" properly */
.leaflet-container img.leaflet-image-layer {
max-width: 15000px !important;
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-tile-pane { z-index: 2; }
.leaflet-objects-pane { z-index: 3; }
.leaflet-overlay-pane { z-index: 4; }
.leaflet-shadow-pane { z-index: 5; }
.leaflet-marker-pane { z-index: 6; }
.leaflet-popup-pane { z-index: 7; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 7;
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile,
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-tile-loaded,
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile,
.leaflet-touching .leaflet-zoom-animated {
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-clickable {
cursor: pointer;
}
.leaflet-container {
cursor: -webkit-grab;
cursor: -moz-grab;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-container,
.leaflet-dragging .leaflet-clickable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline: 0;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-container a.leaflet-active {
outline: 2px solid orange;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a,
.leaflet-bar a:hover {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-control-zoom-out {
font-size: 20px;
}
.leaflet-touch .leaflet-control-zoom-in {
font-size: 22px;
}
.leaflet-touch .leaflet-control-zoom-out {
font-size: 24px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.7);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover {
text-decoration: underline;
}
.leaflet-container .leaflet-control-attribution,
.leaflet-container .leaflet-control-scale {
font-size: 11px;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: content-box;
box-sizing: content-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 19px;
line-height: 1.4;
}
.leaflet-popup-content p {
margin: 18px 0;
}
.leaflet-popup-tip-container {
margin: 0 auto;
width: 40px;
height: 20px;
position: relative;
overflow: hidden;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
padding: 4px 4px 0 0;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
text-decoration: none;
font-weight: bold;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover {
color: #999;
}
.leaflet-popup-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}

File diff suppressed because one or more lines are too long

View File

@@ -24,6 +24,46 @@
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
{% if settings.show_map and album.show_map %}
<link rel="stylesheet" href="{{ theme.url }}/leaflet/leaflet.css" />
<script src="{{ theme.url }}/leaflet/leaflet.js"></script>
<style type="text/css">
.gallery-map-toggle {
cursor: pointer;
}
/* Mimic .galleria-stage to use the same area for the map */
#galleria-map {
position: absolute;
bottom: 80px;
left: 10px;
top: 0;
right: 10px;
}
/* We need to have more contract between active and inactive images
for maps, or we may not know which is the selected. */
.galleria-thumbnails .galleria-image.active {
opacity: 1;
}
.galleria-thumbnails .galleria-image {
opacity: 0.6;
}
.icons {
float: right;
}
#gallery {
clear: both;
}
{% if settings.show_map and album.show_map %}
#fullscreen {
cursor: pointer;
}
{% endif %}
</style>
{% endif %}
</head>
<body>
<div class="container">
@@ -77,11 +117,22 @@
{% if media.exif.focal %}Focal: {{ media.exif.focal }}, {% endif %}
{% if media.exif.exposure %}Exposure: {{ media.exif.exposure }}, {% endif %}
{% if media.exif.fstop %}Fstop: {{ media.exif.fstop }}{% endif %}
{% if media.exif.gps %}
<br/>Location: <a href='https://www.openstreetmap.org/?mlat={{
media.exif.gps.lat }}&amp;mlon={{ media.exif.gps.lon}}&amp;zoom=12&amp;layers=M' target='_blank' class='map'
>{{ 'N{:.6f}'.format(media.exif.gps.lat) if media.exif.gps.lat > 0 else 'S{:.6f}'.format(-media.exif.gps.lat) }}
{{ 'E{:.6f}'.format(media.exif.gps.lon) if media.exif.gps.lon > 0 else 'W{:.6f}'.format(-media.exif.gps.lon) }}</a>
{% endif %}
{% if media.exif.datetime %}
<br>Date: {{ media.exif.datetime }}
{% endif %}
{% endif %}
{%- endmacro %}
{% if settings.show_map and album.show_map %}
<div class="icons">
<a class="gallery-map-toggle"><img src="{{ theme.url }}/img/map.png"/></a>
</div>
{% endif %}
<div id="gallery">
{% for media in album.medias %}
{% if media.type == "image" %}
@@ -138,24 +189,146 @@
<script src="{{ theme.url }}/js/galleria.classic.min.js"></script>
<script src="{{ theme.url }}/js/galleria.history.min.js"></script>
<script>
{% if settings.show_map and album.show_map %}
selected =-1;
function selectMarker(marker) {
if (selected >= 0 && features[selected] != null) {
features[selected].setStyle({
radius: 7,
fillColor: "#888a85",
color: "#2e3436",
weight: 1,
fillOpacity: 1,
clickable: true
});
features[selected].redraw();
}
if (marker != null) {
marker.setStyle({
radius: 7,
fillColor: "#cc0000",
color: "#ef2929",
weight: 1,
fillOpacity: 1,
clickable: true
});
marker.bringToFront();
map.panTo(marker.getLatLng());
}
}
function onFeatureClick(e) {
selectMarker(e.target);
var id = features.indexOf(e.target);
var galleria = $("#gallery").data("galleria");
if (selected != id) {
selected = id;
galleria.show(id);
}
}
function init_map() {
stage = $(".galleria-stage");
/* Make sure we have the map div */
if ($("#galleria-map").length == 0) {
stage.after("<div id='galleria-map'></div>");
map = L.map('galleria-map');
var attrib = '&copy; <a href="https://osm.org/copyright" title="OpenStreetMap" target="_blank">OpenStreetMap</a> contributors | Tiles Courtesy of <a href="https://www.mapquest.com/" title="MapQuest" target="_blank">MapQuest</a> <img src="https://developer.mapquest.com/content/osm/mq_logo.png" width="16" height="16">';
L.tileLayer('https://{s}-s.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
attribution: attrib,
subdomains: ['otile1','otile2','otile3','otile4']
}).addTo( map );
map.setView([51.505, -0.09], 12);
/* Init markers */
features = new Array();
{% for media in album.medias %}
{% if media.exif and media.exif.gps %}
var lat = {{ media.exif.gps.lat }};
var lon = {{ media.exif.gps.lon }};
var marker = L.circleMarker([lat, lon], {
radius: 7,
fillColor: "#888a85",
color: "#2e3436",
weight: 1,
fillOpacity: 1,
clickable: true
});
marker.on('click', onFeatureClick);
features.push(marker);
marker.addTo(map);
{% else %}
features.push(null);
{% endif %}
{% endfor %}
$("#galleria-map").hide();
}
}
{% endif %}
Galleria.configure({
imageCrop: false,
transition: "fade"
});
Galleria.run("#gallery");
{% if settings.show_map and album.show_map %}
function galleryMapToggle() {
/* Make sure the map is loaded */
init_map();
var stage = $(".galleria-stage");
var mapdiv = $("#galleria-map");
if (stage.attr.hidden == true) {
stage.attr.hidden = false;
$(".galleria-info-link").show();
mapdiv.hide();
stage.css("opacity", "1");
/* Change display of the Map link */
$(".gallery-map-toggle img")[0].src = "{{ theme.url }}/img/map.png";
} else {
stage.attr.hidden = true;
$(".galleria-info-link").hide();
mapdiv.show();
/* Play with the stage opacity otherwise galleria will go mad */
stage.css("opacity", "0");
/* Change display of the Map link */
$(".gallery-map-toggle img")[0].src = "{{ theme.url }}/img/photo.png";
}
}
$(".gallery-map-toggle").click(galleryMapToggle);
{% endif %}
Galleria.ready(function() {
this.attachKeyboard({
right: this.next,
left: this.prev,
/* Toggle fullscreen display on press of 'f' key */
0x46: this.toggleFullscreen,
{% if settings.show_map and album.show_map %}
0x4D: galleryMapToggle,
{% endif %}
});
var gallery = this;
$('#fullscreen').click(function() {
gallery.toggleFullscreen();
)};
{% if settings.show_map and album.show_map %}
this.bind('loadfinish', function(e) {
init_map();
if (selected != e.index) {
selectMarker(features[e.index]);
selected = e.index;
}
});
{% endif %}
});
</script>