From fa41e7bf01e166d4fbda548b2e85eb2d11407902 Mon Sep 17 00:00:00 2001 From: Martijn Versteegh Date: Wed, 28 Nov 2018 19:40:19 +0100 Subject: [PATCH] Add utility to create zoom pyramids and write a leaflet html file. --- buildpyramid/Readme.txt | 11 ++ buildpyramid/buildpyramid.cpp | 266 ++++++++++++++++++++++++++++++++++ buildpyramid/makefile | 142 ++++++++++++++++++ 3 files changed, 419 insertions(+) create mode 100644 buildpyramid/Readme.txt create mode 100644 buildpyramid/buildpyramid.cpp create mode 100644 buildpyramid/makefile diff --git a/buildpyramid/Readme.txt b/buildpyramid/Readme.txt new file mode 100644 index 0000000..777d0e6 --- /dev/null +++ b/buildpyramid/Readme.txt @@ -0,0 +1,11 @@ +usage: buildpyramid + +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. + diff --git a/buildpyramid/buildpyramid.cpp b/buildpyramid/buildpyramid.cpp new file mode 100644 index 0000000..9237d3f --- /dev/null +++ b/buildpyramid/buildpyramid.cpp @@ -0,0 +1,266 @@ +#include +#include +#include +#include +#include +#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 \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<\n" +"\n" +"\n" +"\tMinetestMapper\n" +"\t\n" +"\t\n" +"\t\n" +"\t\n" +"\t\n" +"\t\n" +"\n" +"\n" +"
\n" +"\n" +"" +"\n" +"\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); +} + + diff --git a/buildpyramid/makefile b/buildpyramid/makefile new file mode 100644 index 0000000..a8e1572 --- /dev/null +++ b/buildpyramid/makefile @@ -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