346 lines
12 KiB
Lua
346 lines
12 KiB
Lua
--[[
|
|
This file is part of darktable,
|
|
Copyright 2016 by Tobias Jakobs.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
]]
|
|
--[[
|
|
darktable geoJSON export script
|
|
|
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
|
* mkdir
|
|
* convert (ImageMagick)
|
|
* xdg-open
|
|
* xdg-user-dir
|
|
|
|
WARNING
|
|
This script is only tested with Linux
|
|
|
|
USAGE
|
|
* require this script from your main Lua file
|
|
|
|
]]
|
|
|
|
local dt = require "darktable"
|
|
local du = require "lib/dtutils"
|
|
local df = require "lib/dtutils.file"
|
|
local gettext = dt.gettext
|
|
|
|
du.check_min_api_version("7.0.0", "geoJSON_export")
|
|
|
|
-- return data structure for script_manager
|
|
|
|
local script_data = {}
|
|
|
|
script_data.destroy = nil -- function to destory the script
|
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
|
|
|
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
gettext.bindtextdomain("geoJSON_export",dt.configuration.config_dir.."/lua/locale/")
|
|
|
|
local function _(msgid)
|
|
return gettext.dgettext("geoJSON_export", msgid)
|
|
end
|
|
|
|
-- Sort a table
|
|
local function spairs(_table, order) -- Code copied from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
|
|
-- collect the keys
|
|
local keys = {}
|
|
for _key in pairs(_table) do keys[#keys + 1] = _key end
|
|
|
|
-- if order function given, sort by it by passing the table and keys a, b,
|
|
-- otherwise just sort the keys
|
|
if order then
|
|
table.sort(keys, function(a,b) return order(_table, a, b) end)
|
|
else
|
|
table.sort(keys)
|
|
end
|
|
|
|
-- return the iterator function
|
|
local i = 0
|
|
return function()
|
|
i = i + 1
|
|
if keys[i] then
|
|
return keys[i], _table[keys[i]]
|
|
end
|
|
end
|
|
end
|
|
|
|
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
|
|
dt.print(string.format(_("Export Image %i/%i"), number, total))
|
|
end
|
|
|
|
local function create_geoJSON_file(storage, image_table, extra_data)
|
|
if not df.check_if_bin_exists("mkdir") then
|
|
dt.print_error(_("mkdir not found"))
|
|
return
|
|
end
|
|
if not df.check_if_bin_exists("convert") then
|
|
dt.print_error(_("convert not found"))
|
|
return
|
|
end
|
|
if not df.check_if_bin_exists("xdg-open") then
|
|
dt.print_error(_("xdg-open not found"))
|
|
return
|
|
end
|
|
if not df.check_if_bin_exists("xdg-user-dir") then
|
|
dt.print_error(_("xdg-user-dir not found"))
|
|
return
|
|
end
|
|
|
|
dt.print_error("Will try to export geoJSON file now")
|
|
|
|
local exportDirectory = dt.preferences.read("geoJSON_export","ExportDirectory","string")
|
|
|
|
-- Creates dir if not exsists
|
|
local imageFoldername = "files/"
|
|
local mkdirCommand = "mkdir -p "..exportDirectory.."/"..imageFoldername
|
|
dt.control.execute( mkdirCommand)
|
|
|
|
-- Create the thumbnails
|
|
for image,exported_image in pairs(image_table) do
|
|
if ((image.longitude and image.latitude) and
|
|
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
|
|
) then
|
|
local path, filename, filetype = string.match(exported_image, "(.-)([^\\/]-%.?([^%.\\/]*))$")
|
|
filename = string.upper(string.gsub(filename,"%.%w*", ""))
|
|
|
|
-- convert -size 92x92 filename.jpg -resize 92x92 +profile "*" thumbnail.jpg
|
|
-- In this example, '-size 120x120' gives a hint to the JPEG decoder that the image is going to be downscaled to
|
|
-- 120x120, allowing it to run faster by avoiding returning full-resolution images to GraphicsMagick for the
|
|
-- subsequent resizing operation. The '-resize 120x120' specifies the desired dimensions of the output image. It
|
|
-- will be scaled so its largest dimension is 120 pixels. The '+profile "*"' removes any ICM, EXIF, IPTC, or other
|
|
-- profiles that might be present in the input and aren't needed in the thumbnail.
|
|
|
|
local convertToThumbCommand = "convert -size 96x96 "..exported_image.." -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" "..exportDirectory.."/"..imageFoldername.."thumb_"..filename..".jpg"
|
|
dt.control.execute( convertToThumbCommand)
|
|
local concertCommand = "convert -size 438x438 "..exported_image.." -resize 438x438 +profile \"*\" "..exportDirectory.."/"..imageFoldername..filename..".jpg"
|
|
dt.control.execute( concertCommand)
|
|
end
|
|
|
|
-- delete the original image
|
|
os.remove(exported_image)
|
|
|
|
local pattern = "[/]?([^/]+)$"
|
|
filmName = string.match(image.film.path, pattern)
|
|
end
|
|
|
|
local exportgeoJSONFilename = filmName..".geoJSON"
|
|
local exportMapBoxHTMLFilename = filmName..".html"
|
|
|
|
-- Create the geoJSON file
|
|
local geoJSON_file = [[
|
|
{
|
|
"type": "FeatureCollection",
|
|
"features": [
|
|
]]
|
|
|
|
for image,exported_image in pairs(image_table) do
|
|
--filename = string.upper(string.gsub(image.filename,"%.", "_"))
|
|
-- Extract filename, e.g DSC9784.ARW -> DSC9784
|
|
filename = string.upper(string.gsub(image.filename,"%.%w*", ""))
|
|
-- Extract extension from exported image (user can choose JPG or PNG), e.g DSC9784.JPG -> .JPG
|
|
extension = string.match(exported_image,"%.%w*$")
|
|
|
|
if ((image.longitude and image.latitude) and
|
|
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
|
|
) then
|
|
|
|
local image_title, image_description
|
|
if (image.title and image.title ~= "") then
|
|
image_title = image.title
|
|
else
|
|
image_title = image.filename
|
|
end
|
|
image_description = image.description
|
|
|
|
geoJSON_file = geoJSON_file..
|
|
[[ {
|
|
"type": "Feature",
|
|
"geometry": {
|
|
"type": "Point",
|
|
]]
|
|
geoJSON_file = geoJSON_file.." \"coordinates\": ["..string.gsub(tostring(image.longitude),",", ".")..","..string.gsub(tostring(image.latitude),",", ".").."]\n"
|
|
geoJSON_file = geoJSON_file..
|
|
[[ },
|
|
"properties": {
|
|
"title": "]]..image_title..[[",
|
|
"description": "]]..image_description..[[",
|
|
"image": "]]..imageFoldername..filename..[[.jpg",
|
|
"icon": {
|
|
"iconUrl": "]]..imageFoldername.."thumb_"..filename..[[.jpg",
|
|
"iconSize": [96, 65],
|
|
"iconAnchor": [48, 32],
|
|
"popupAnchor": [0, -32],
|
|
"className": "dot"
|
|
}
|
|
}
|
|
}
|
|
,]]
|
|
end
|
|
end
|
|
|
|
geoJSON_file = geoJSON_file:sub(0,geoJSON_file:len()-1)
|
|
geoJSON_file = geoJSON_file..
|
|
[[
|
|
]
|
|
}
|
|
]]
|
|
|
|
|
|
--HTML
|
|
if ( dt.preferences.read("geoJSON_export","CreateMapBoxHTMLFile","bool") == true ) then
|
|
local mapBoxKey = dt.preferences.read("geoJSON_export","mapBoxKey","string")
|
|
local mapBoxHTML_file = [[
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset=utf-8 />
|
|
<title>]]..filmName..[[</title>
|
|
<script src='https://api.tiles.mapbox.com/mapbox.js/v2.2.4/mapbox.js'></script>
|
|
<link href='https://api.tiles.mapbox.com/mapbox.js/v2.2.4/mapbox.css' rel='stylesheet' />
|
|
<style>
|
|
body { margin:0; padding:0; }
|
|
.map { position:absolute; top:0; bottom:0; width:100%; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id='map-tooltips-js' class='map'> </div>
|
|
|
|
<script>
|
|
L.mapbox.accessToken = ']]..mapBoxKey..[[';
|
|
var mapTooltipsJS = L.mapbox.map('map-tooltips-js', 'mapbox.light')
|
|
.setView([0,0], 5);
|
|
|
|
var myLayer = L.mapbox.featureLayer()
|
|
.loadURL(']]..exportgeoJSONFilename..[[')
|
|
.addTo(mapTooltipsJS);
|
|
|
|
// Set a custom icon on each marker based on feature properties.
|
|
myLayer.on('layeradd', function(e) {
|
|
var marker = e.layer,
|
|
feature = marker.feature;
|
|
marker.setIcon(L.icon(feature.properties.icon));
|
|
var content = '<h2>'+ feature.properties.title+'<\/h2>' + '<p>'+ feature.properties.description+'<\/p>' + '<img src="'+feature.properties.image+'" alt="">';
|
|
marker.bindPopup(content,{minWidth: 457});
|
|
});
|
|
// Center on click
|
|
myLayer.on('click', function(e) {
|
|
mapTooltipsJS.panTo(e.layer.getLatLng());
|
|
});
|
|
|
|
// https://www.mapbox.com/mapbox.js/example/v1.0.0/fit-map-to-markers/
|
|
// Especially with dynamic data, it's useful to automatically fit all markers
|
|
// in the map bounds instead of guessing and checking center and zoom values
|
|
|
|
// Since this layer is loaded with the asynchronous method loadURL, that uses
|
|
// AJAX in the background, we wait for all of the markers to be loaded by
|
|
// waiting for the ready event. If you don't load your markers with an async
|
|
// method and instead set them with setGeoJSON or similar, you don't need
|
|
// to do this.
|
|
myLayer.on('ready', function() {
|
|
// featureLayer.getBounds() returns the corners of the furthest-out markers,
|
|
// and map.fitBounds() makes sure that the map contains these.
|
|
mapTooltipsJS.fitBounds(myLayer.getBounds());
|
|
});
|
|
|
|
// Lines
|
|
// https://www.mapbox.com/mapbox.js/example/v1.0.0/line-marker/
|
|
myLayer.on('ready', function(e) {
|
|
var line = [];
|
|
|
|
this.eachLayer(function(marker) {
|
|
line.push(marker.getLatLng());
|
|
});
|
|
|
|
var polyline_options = {
|
|
color: '#FF0000'
|
|
};
|
|
|
|
var polyline = L.polyline(line, polyline_options).addTo(mapTooltipsJS);
|
|
});
|
|
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|
|
]]
|
|
|
|
local file = io.open(exportDirectory.."/"..exportMapBoxHTMLFilename, "w")
|
|
file:write(mapBoxHTML_file)
|
|
file:close()
|
|
end
|
|
|
|
local file = io.open(exportDirectory.."/"..exportgeoJSONFilename, "w")
|
|
file:write(geoJSON_file)
|
|
file:close()
|
|
|
|
dt.print("geoJSON file created in "..exportDirectory)
|
|
|
|
-- Open the file with the standard programm
|
|
if ( dt.preferences.read("geoJSON_export","OpengeoJSONFile","bool") == true ) then
|
|
local geoJSONFileOpenCommand
|
|
geoJSONFileOpenCommand = "xdg-open "..exportDirectory.."/\""..exportgeoJSONFilename.."\""
|
|
dt.control.execute( geoJSONFileOpenCommand)
|
|
end
|
|
|
|
|
|
end
|
|
|
|
local function destroy()
|
|
dt.destroy_storage("geoJSON_export")
|
|
end
|
|
|
|
-- Preferences
|
|
dt.preferences.register("geoJSON_export",
|
|
"CreateMapBoxHTMLFile",
|
|
"bool",
|
|
_("geoJSON export: Create an additional HTML file"),
|
|
_("Creates a HTML file, that loads the geoJASON file. (Needs a MapBox key"),
|
|
false )
|
|
dt.preferences.register("geoJSON_export",
|
|
"mapBoxKey",
|
|
"string",
|
|
_("geoJSON export: MapBox Key"),
|
|
_("https://www.mapbox.com/studio/account/tokens"),
|
|
'' )
|
|
dt.preferences.register("geoJSON_export",
|
|
"OpengeoJSONFile",
|
|
"bool",
|
|
_("geoJSON export: Open geoJSON file after export"),
|
|
_("Opens the geoJSON file after the export with the standard program for geoJSON files"),
|
|
false )
|
|
|
|
local handle = io.popen("xdg-user-dir DESKTOP")
|
|
local result = handle:read()
|
|
handle:close()
|
|
if (result == nil) then
|
|
result = ""
|
|
end
|
|
dt.preferences.register("geoJSON_export",
|
|
"ExportDirectory",
|
|
"directory",
|
|
_("geoJSON export: Export directory"),
|
|
_("A directory that will be used to export the geoJSON files"),
|
|
result )
|
|
|
|
-- Register
|
|
dt.register_storage("geoJSON_export", "geoJSON Export", nil, create_geoJSON_file)
|
|
|
|
script_data.destroy = destroy
|
|
|
|
return script_data
|