Add utility to create zoom pyramids and write a leaflet html file.

master
Martijn Versteegh 2018-11-28 19:40:19 +01:00
parent 45f285218d
commit fa41e7bf01
3 changed files with 419 additions and 0 deletions

11
buildpyramid/Readme.txt Normal file
View File

@ -0,0 +1,11 @@
usage: buildpyramid <metadatafile> <outputname>
Reads the metadata file (output by minetestmapper) describing how many tiles are available and writes a full zoom pyramid and some HTML code for use with leaflet.js to display a 'slippy' map.
just copy leaflet.js and leaflet.css form leaflet into the same folder and you whould have a working map.
Outputname should end in .jpg (recommended) or .png.
buildpyramid can't handle subdirectories. run it from the directory containing your minetestmapper output. it will write your map to the same folder.

View File

@ -0,0 +1,266 @@
#include <math.h>
#include <assert.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include "../include/Image.h"
void outputLeafletCode(std::string const &output, int maxLevel, int tileSize);
int buildPyramid(std::string const &baseName, std::string const &out, Image *im, int minTileX,int minTileY, int maxTileX, int maxTileY, int level, int divisor, bool leafletMode)
{
int count = 0;
Color empty(0,0,0,0);
im->fill(empty, true);
if ((maxTileX - minTileX) == 1)
{
assert((maxTileY - minTileY) == 1);
std::ostringstream inf;
inf << minTileX << "_" << minTileY << "_" << baseName;
try
{
Image in(inf.str());
if (in.GetWidth())
{
in.blit(im, 0,0);
count++;
}
}
catch (std::runtime_error const &e)
{
// image is allowed not to exist, just return 0;
return 0;
}
}
else
{
Image subTile(im->GetWidth(), im->GetHeight());
int halfX = minTileX + (maxTileX - minTileX)/2;
int halfY = minTileY + (maxTileY - minTileY)/2;
int c = buildPyramid(baseName, out, &subTile, minTileX, minTileY, halfX, halfY, level +1, divisor / 2, leafletMode);
if (c)
{
subTile.scaleBlit(im, 0, im->GetHeight()/2, im->GetWidth()/2, im->GetHeight() / 2);
count+=c;
}
c = buildPyramid(baseName, out, &subTile, halfX, minTileY, maxTileX, halfY, level +1, divisor / 2, leafletMode);
if (c)
{
subTile.scaleBlit(im, im->GetWidth()/2, im->GetHeight()/2, im->GetWidth()/2, im->GetHeight() / 2);
count+=c;
}
c = buildPyramid(baseName, out, &subTile, minTileX, halfY, halfX, maxTileY, level +1, divisor / 2, leafletMode);
if (c)
{
subTile.scaleBlit(im, 0, 0, im->GetWidth()/2, im->GetHeight() / 2);
count+=c;
}
c= buildPyramid(baseName, out, &subTile, halfX, halfY, maxTileX, maxTileY, level +1, divisor / 2, leafletMode);
if (c)
{
subTile.scaleBlit(im, im->GetWidth()/2, 0, im->GetWidth()/2, im->GetHeight() / 2);
count+=c;
}
}
if (count)
{
std::ostringstream of;
of << level << "_" << (minTileX / divisor) << "_" << ((leafletMode ? -1 : 1) * minTileY/divisor - (leafletMode ? 1 : 0)) << "_" << out;
std::cout << "Writing image: " << of.str() << std::endl;
im->save(of.str());
}
return count;
}
int main(int argc, char **argv)
{
if (argc !=3)
{
std::cerr << "Usage: buildpyramid <metadatafile> <outname>\n" << std::endl;
exit(1);
}
std::ifstream mt;
mt.open(argv[1], std::ios::in);
if (!mt.is_open())
{
std::cerr << "Couldn't open file '\n" << argv[1] << "'" << std::endl;
exit(1);
}
std::string baseName;
int numTilesX, numTilesY, minTileX, minTileY, tileSizeX, tileSizeY;
int count=0;
while (true)
{
std::string label;
mt >> label;
if (mt.eof())
{
std::cerr << "Error parsing metadata file\n" << std::endl;
exit(1);
}
if (label == "BaseName:")
{
mt >> baseName;
count++;
}
else if (label == "NumTiles:")
{
mt >> numTilesX >> numTilesY;
count++;
}
else if (label == "MinTile:")
{
mt >> minTileX >> minTileY;
count++;
}
else if (label == "TileSize:")
{
mt >> tileSizeX >> tileSizeY;
count++;
}
if (count >3)
{
break;
}
}
if (tileSizeX != tileSizeY)
{
std::cerr << "Can't handle non-square tiles." << std::endl;
exit(1);
}
int maxDim = numTilesX + minTileX;
maxDim = -minTileX > maxDim ? -minTileX : maxDim;
maxDim = numTilesY + minTileY > maxDim ? numTilesY - minTileY : maxDim;
maxDim = -minTileY > maxDim ? -minTileY : maxDim;
assert(maxDim >0);
int maxLevel;
// round up to power of 2
for (int i = 0; i < 62 ; i++)
{
if ((1 << i) >= maxDim)
{
maxDim = 1<<i;
maxLevel = i;
break;
}
}
Image im(tileSizeX, tileSizeY);
std::ostringstream of;
std::string out(argv[2]);
buildPyramid(baseName, out, &im, 0,0, maxDim, maxDim, 0, maxDim, true);
buildPyramid(baseName, out, &im, -maxDim,0, 0, maxDim, 0, maxDim, true);
buildPyramid(baseName, out, &im, -maxDim,-maxDim, 0,0, 0, maxDim, true);
buildPyramid(baseName, out, &im, 0,-maxDim, maxDim, 0, 0, maxDim, true);
outputLeafletCode(out, maxLevel, tileSizeX);
return 0;
}
static char const *leafletMapHtml =
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
"\t<title>MinetestMapper</title>\n"
"\t<meta charset=\"utf-8\" />\n"
"\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
"\t<!-- link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"favicon.ico\" /-->\n"
"\t<link rel=\"stylesheet\" href=\"leaflet.css\" integrity=\"sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA==\" crossorigin=\"\"/>\n"
"\t<script src=\"leaflet.js\" integrity=\"sha512-nMMmRyTVoLYqjP9hrbed9S+FzjZHW5gY1TWCHA5ckwXZBadntCNs8kEqAWdrb9O7rxbCaA4lKTIWjDXZxflOcA==\" crossorigin=\"\"></script>\n"
"\t<style> .labelclass{position: absolute; background: rgba(255,0,255,0); font-size:20px;}</style>\n"
"</head>\n"
"<body>\n"
"<div id=\"mapid\" style=\"width: 90vw; height: 90vh;\"></div>\n"
"<script>\n"
"\tvar MineTestMap = L.map('mapid', {\n"
"\tcrs: L.CRS.Simple,\n"
"\t});\n"
"\tMineTestMap.setView([0.0, 0.0], %d);\n"
"\tL.tileLayer('{z}_{x}_{y}_%s', {\n"
"\t\tminNativeZoom: 0,\n"
"\t\tmaxNativeZoom: %d,\n"
"\t\tattribution: 'Minetest World',\n"
"\t\ttileSize: %d,\n"
"\t\terrorTileUrl: \"empty_tile_%s\",\n"
"\t\t}).addTo(MineTestMap);\n"
"\tvar popup = L.popup();\n"
"\tvar mapZoom = %f;\n"
"\tfunction onMapClick(e) {\n"
"\t\tvar scaledPos = L.latLng(e.latlng.lat / mapZoom, e.latlng.lng / mapZoom);\n"
"\t\tpopup\n"
"\t\t\t.setLatLng(e.latlng)\n"
"\t\t\t.setContent(\"You clicked the map at \"+ scaledPos.toString())\n"
"\t\t\t.openOn(MineTestMap);\n"
"\t}\n"
"\tMineTestMap.on('click', onMapClick);\n"
"</script>\n"
"<script src=\"markers.js\" defer></script>"
"</body>\n"
"</html>\n";
void outputLeafletCode(std::string const &output, int maxLevel, int tileSize)
{
std::ostringstream fn;
fn << output << ".html";
// I use fopen instead of ostr because I need fprintf to put the correct values into the
// html static string above. ostream is nice and C++ and all, but formatted output handling
// for streams is retarded. Use the best tool for the job.
FILE *out = fopen(fn.str().c_str(), "w");
if (!out)
{
std::cout << "error opening file:" << fn.str() << std::endl;
return;
}
fprintf(out, leafletMapHtml, maxLevel, output.c_str(), maxLevel, tileSize, output.c_str(), 1.0/pow(2,maxLevel));
fclose(out);
}

142
buildpyramid/makefile Normal file
View File

@ -0,0 +1,142 @@
# automagic makefile with auto dependency generation
# usage:
# - fill out the needed info (object filesneeded libs CXX flags etc)
# - run 'make fixbuild' the first time you use the makefile this creates the DEPDIR and OBJDIR directories
# - run 'make depend' each time you added a new cpp to the project, after the dependency file is generated it will be kept up-to-date automatically
# - 'make' will build your program
# - make clean will delete the object files and the executable
# - make veryclean will delete all generated files (also core files and *~ and *.bkp)
CPP_OBJECTS_BARE= ../Image buildpyramid
C_OBJECTS_BARE=
LIBS= -lgd
PROGNAME=buildpyramid
CC=gcc
CXX=g++
LD=g++
CFLAGS = -Wall -Werror -g -I ../include
CXXFLAGS = $(CFLAGS)
LDFLAGS = -g
-include presets.mk
RM=rm -f
RMDIR=rm -rf
.PHONY: clean depend veryclean fixbuild
OBJDIR=obj
DEPDIR=dep
# if all you srcfiles are in one subdir, you can list it here to spare you the typing in CPP_OBJECTS_BARE
# otherwise put . here for the current dir
SRCDIR=.
####################################################################################################################################
# no configuration beyond this point
####################################################################################################################################
#create a unique filename from a path
filefrompath=$(subst /,_,$(subst .,_,$(1)))
# how to create various filenames from the bare name
objfile=$(OBJDIR)/$(call filefrompath,$(1)).o
depfile=$(DEPDIR)/$(call filefrompath,$(1)).dep
cppfile=$(SRCDIR)/$(1).cpp
cfile=$(SRCDIR)/$(1).c
# build a rule to create an object from a cpp file, input is the bare filename
define makeobjrule
$(call objfile,$(1)):$(call cppfile,$(1))
$(CXX) $(CXXFLAGS) -c $$< -o $$@
endef
# build a rule to create an object from a c file, input is the bare filename
define cmakeobjrule
$(call objfile,$(1)):$(call cfile,$(1))
$(CC) $(CFLAGS) -c $$< -o $$@
endef
#build a rule to create a depfile from a cpp file, bare name is input
define makedeprule
$(call depfile,$(1)):$(call cppfile,$(1))
$(CXX) $(CXXFLAGS) -c $$< -MM -MF $$@ -MQ $(call objfile,$(1))
endef
#build a rule to create a depfile from a cpp file, bare name is input
define cmakedeprule
$(call depfile,$(1)):$(call cfile,$(1))
$(CC) $(CFLAGS) -c $$< -MM -MF $$@ -MQ $(call objfile,$(1))
endef
# generate a list of all cpp objectfiles
CPPOBJECTS=$(foreach f,$(CPP_OBJECTS_BARE),$(call objfile,$(f)))
# generate a list of all c objectfiles
COBJECTS=$(foreach f,$(C_OBJECTS_BARE),$(call objfile,$(f)))
# generate a list of all depfile
CPPDEPS=$(foreach f,$(CPP_OBJECTS_BARE),$(call depfile,$(f)))
# generate a list of all depfile
CDEPS=$(foreach f,$(C_OBJECTS_BARE),$(call depfile,$(f)))
# generate rules for all object files
CPPOBJRULES=$(foreach f,$(CPP_OBJECTS_BARE),$(call makeobjrule,$(f)))
# generate rules for all object files
COBJRULES=$(foreach f,$(C_OBJECTS_BARE),$(call cmakeobjrule,$(f)))
# generate rules for all DEP files
CPPDEPRULES=$(foreach f,$(CPP_OBJECTS_BARE),$(call makedeprule,$(f)))
# generate rules for all DEP files
CDEPRULES=$(foreach f,$(C_OBJECTS_BARE),$(call cmakedeprule,$(f)))
$(PROGNAME) :fixbuild $(DEPDIR)/depsuptodate $(CPPOBJECTS) $(COBJECTS)
$(LD) $(LDFLAGS) $(CPPOBJECTS) $(COBJECTS) $(LIBS) -o $(PROGNAME)
depend: $(CPPDEPS) $(CDEPS) fixbuild
# rm $(DEPDIR)/\*.dep
veryclean: clean
$(RMDIR) dep obj
$(RM) core core.* *~ *bkp
$(DEPDIR)/depsuptodate: depend makefile fixbuild
touch $(DEPDIR)/depsuptodate
clean:
$(RM) $(CPPOBJECTS) $(COBJECTS) $(PROGNAME)
fixbuild: $(DEPDIR) $(OBJDIR)
$(DEPDIR):
mkdir -p $(DEPDIR)
$(OBJDIR):
mkdir -p $(OBJDIR)
$(eval $(CPPOBJRULES))
$(eval $(CPPDEPRULES))
$(eval $(COBJRULES))
$(eval $(CDEPRULES))
-include $(DEPDIR)/*.dep