#!/bin/bash # # journeymap_join [options] [--] [output_basename] # # Make a Deepzoom image (DZI) of a given set of Minecraft Journey Maps. # # The original time maps are symlinked into the image to preserve space (around # 1/2 the disk space of the final image) while the other 'levels' of the image # are created from those maps (as needed). This makes major use of the # 'sparse tile image' ability of the deepzoom image, and preserves the # transparency of the maps. # # COMMON RUNS: # journeymap_join -mw Server # overworld daytime map # # OPTIONS: # -mw set The map world (def: CubeKrowd) # -md set The dimention of the map world: one of o,e,n # -ms set Set of maps to use (day,night,topo,1,2,3,...) # -mp path Just use this path to the map numbers (overrides -s set) # The output file basename will be required! # # -ls List the map directory and exit # # -l Limit the size of the map (around center) remove outliers # -c #,# journey map tile to be centered in DZI image # (if center of minecraft world is not 0,0) # NB: jouneymap tile coordes are world coords / 512 # CAUTION: this must not change when updating image. # # -- end of options (leaving just the optional output filename) # ### # Viewing Deepzoom Images... # # A HTML file to display the deepzoom image is also generated using # openseadragon. You will need to download that java script from... # https://openseadragon.github.io/ # Below is a setting to define the path where the package is located. # Currently assumes it is in directory "openseadraon" in same directory. # or at least a symlink to the package. # # To see image (and avoid Crosssite Origin Reference failure), # Run python server... # python3 -m http.server 2>/dev/null & # then point your web browser to # http://localhost:8000/ # and select the generated ".html" for the image. # # If the image is not visible check the web browser inspection console for # errors. # ### # Programers Notes... # # Hardcoded in script below... # * Location of OpenSeaDragon javascript used by the HTML # * Location of JourneyMaps save directory # Typically this is ~/.minecraft/journeymap # * Size and Limits of the world, in terms of tile indexes # This could be determined, and adjusted to a power of 2! # # Anthony Thyssen 5 June 2024 # https://antofthy.gitlab.io/software/#journeymap_dzi # # Discover where the shell script resides PROGNAME=`type "$0" | awk '{print $3}'` # search for executable on path PROGDIR="${PROGNAME%/*}" # extract directory of program PROGNAME="${PROGNAME##*/}" # base name of program ORIGDIR=`pwd -P` # original directory Usage() { # Report error and Synopsis line only echo >&2 "$PROGNAME:" "$@" sed >&2 -n '1,2d; /^###/q; /^#/!q; /^#$/q; s/^# */Usage: /p;' \ "$PROGDIR/$PROGNAME" exit 10; } Help() { # Output Full header comments as documentation sed >&2 -n '1d; /^###/q; /^#/!q; s/^#//; s/^ //; p' \ "$PROGDIR/$PROGNAME" exit 10; } Error() { # Just output an error condition and exit (no usage) echo >&2 "$PROGNAME:" "$@" exit 2 } # Log base 2 function (use imagemagick as we need it anyway) log2() { identify -format "%[fx: ceil( log($1)/log(2) ) ]\n" null:; } # Default Paths... #JOURNEY_MAPS="$HOME/.minecraft/journeymap/data/mp" JOURNEY_MAPS="$HOME/apps/journeymap/data/mp" JOURNEY_MAPS_SP="$HOME/apps/journeymap/data/sp" # Where on website (absolute or relative to DZI file) is the OpenSeadragon # package. Directory with "openseadragon.min.js" and its "images" OPEN_SEADRAGON="openseadragon" # Default map to use # Determine world default based on my current location #MAP_WORLD="Faviorate~Server" case $(pwd) in */servers) MAP_WORLD="Flatland" JOURNEY_MAPS=$JOURNEY_MAPS_SP ;; */CoreJourney) MAP_WORLD="Core~Journey" ;; */Element*) MAP_WORLD="Element~Animation~Server" ;; */CubeKrowd/su*) MAP_WORLD="CubeKrowd" ;; # server in maps area */CubeKrowd/Creative) MAP_WORLD="CubeKrowd_cr" ;; # server in maps area esac # Default map the overworld (dimension 0) and the day time map MAP_DIM="overworld" MAP_SET="day" SIZE=512 # size of the journeymap tiles CENTER_X=0 # Default Center of the world (in tile coords) CENTER_Y=0 # for limit option if set LIMIT=-1 # no limits for now # Level which has the largest single tile image (SIZE of 512 => 9 )... ONE_TILE_LEVEL=$( log2 $SIZE ) # option handling while [ $# -gt 0 ]; do case "$1" in # Standard help option. -\?|-help|--help|--doc*) Help ;; -mw) shift; MAP_WORLD="$1" ;; # The map world to look at -md) shift; MAP_DIM="$1" ;; # The dimension of that world (o,e,n) -ms) shift; MAP_SET="$1" ;; # Map sub-set of the world (d,n,t) -mp) shift; MAP_PATH="$1" ;; # directory use this path to map files -c) if [[ "$2" =~ ([-+]?[0-9]+),([-+]?[0-9]+) ]]; then CENTER_X="${BASH_REMATCH[1]}" # note center is in map tile coords CENTER_Y="${BASH_REMATCH[2]}" else Usage "Bad Geometry" fi ; shift ;; -l) shift; LIMIT=$1 ;; # limit disance from center (remove outliers) --) shift; break ;; # forced end of user options -*) Usage "Unknown option \"$1\"" ;; *) break ;; # unforced end of user options esac shift # next option done # Only option left is a optional destination filename (without suffix) [[ $# -ge 2 ]] && Usage "Too Many Arguments" [[ -v MAP_PATH && $# -eq 0 ]] && Usage "Output file basename is required with map path (-mp) option" # Work out map path from map options if [[ ! -v MAP_PATH ]]; then if [[ -z "$MAP_WORLD" || ! -d $JOURNEY_MAPS/$MAP_WORLD ]]; then echo >&2 "$PROGNAME: World (Server) not set (-mw \"$MAP_WORLD\")" echo >&2 "Select from..." ls >&2 $JOURNEY_MAPS exit 2 fi # Set default output filename case "$MAP_DIM" in o|0|overworld) MAP_DIM=overworld ;; e|1|end) MAP_DIM=the_end; OUTPUT=${MAP_WORLD//\~/_}_end ;; n|-1|nether) MAP_DIM=the_nether; OUTPUT=${MAP_WORLD//\~/_}_nether ;; *) if [[ ! -v $MAP_PATH ]]; then echo >&2 "PROGNAME: Unknown dimension (-md \"$MAP_DIM\")" echo >&2 "Select from..." ls >&2 $JOURNEY_MAPS/$MAP_WORLD exit 2 fi ;; esac if [ "X$MAP_DIM" == 'Xoverworld' ]; then case "$MAP_SET" in d|day) MAP_SET=day ;; # default n|night) MAP_SET=night ;; t|topo) MAP_SET=topo ;; [0-7]|-[1-4]) ;; # underground level maps *) if [[ ! -v $MAP_PATH ]]; then echo >&2 "$PROGNAME: Unknown Map Sub-Set (-ms \"$MAP_SET\")" echo >&2 "Select from..." ls >&2 $JOURNEY_MAPS/$MAP_WORLD/$MAP_DIM exit 2 fi ;; esac # adjust output filename (if not given) case "$MAP_SET" in [0-7]|-[1-4]) OUTPUT=${MAP_WORLD//\~/_}_ugnd_${MAP_SET} ;; *) OUTPUT=${MAP_WORLD//\~/_}_${MAP_SET} ;; esac fi # the directory of maps we are deep zooming MAP_PATH="$JOURNEY_MAPS/$MAP_WORLD/$MAP_DIM/$MAP_SET" fi echo "Map Path: $MAP_PATH" [[ -d "$MAP_PATH" ]] || Error "Unable to locate Journal Map Images!" # just list the map directory if [ "$LIST_MAPS" ]; then ls "$MAP_PATH" exit 0 fi # If a argument is given, override the output filename OUTPUT="${1:-$OUTPUT}" # --------------------------------------------------------------------------- # # Get map limits (to determine zoom depth)... # echo "Checking map limits..." min_x=9999 min_y=9999 max_x=-9999 max_y=-9999 for map in "$MAP_PATH"/*,*.png; do # Extract the tile numbers (maybe negative) from image name. read x y <<< $( echo "${map##*/}" | tr -cs '0-9-' ' ' ) # echo " -> $x $y" if (( LIMIT >= 0 )); then (( x > CENTER_X + LIMIT )) && continue (( y > CENTER_Y + LIMIT )) && continue (( x < CENTER_X - LIMIT )) && continue (( y < CENTER_Y - LIMIT )) && continue fi (( min_x > x )) && min_x=$x (( min_y > y )) && min_y=$y (( max_x < x )) && max_x=$x (( max_y < y )) && max_y=$y done if (( max_x == -9999 )); then if (( LIMIT >= 0 )); then Error "No Maps Found using given center and limit" else Error "No Maps Found in given world or map path" fi fi # If center point is outside range, and limits aren't in use then re-center the # maps in the DZI image. This is dangerious as the center could shift, # requiring a complete re-build of the deep zoom image. # if (( LIMIT < 0 )); then # center outside Y range! -- recenter in range if (( CENTER_X < min_x || CENTER_X > max_x )); then echo "Re-centering X" CENTER_X=$(( (min_x + max_x)/2 )) fi if (( CENTER_Y < min_y || CENTER_Y > max_y )); then echo "Re-centering Y" CENTER_Y=$(( (min_y + max_y)/2 )) fi fi # work out the number of levels in full map image range_x=$(( max_x - min_x +1 )) range_y=$(( max_y - min_y +1 )) echo "Journeymap Tile Range:" \ " $min_x to $max_x ($range_x) , $min_y to $max_y ($range_y)"; if (( LIMIT >= 0 )); then tile_range=$(( LIMIT*2 )) else # no limit size use lager of x,y ranges tile_range=$((range_x > range_y ? range_x : range_y)) fi unset range_x range_y # get maximum number of levels in image (journeymap tile image level) MAX_MAP_LEVEL=$( log2 "$SIZE*$tile_range" ) # Get the number of tile images posible this level pyramid image_size=$(( 2**$MAX_MAP_LEVEL )) # size of full DZI image in pixels echo "Map Image Depth: $MAX_MAP_LEVEL" \ " Or $image_size pixels or $((image_size/SIZE)) tiles max" # # Create the top level 'dzi' file # if [[ -f "$OUTPUT.dzi" ]]; then echo "Checking existing \"$OUTPUT.dzi\""... if grep -q "Size\>.*\.*\' echo '' echo ' ' echo '' ) > "$OUTPUT.dzi" fi # Offset of journeymap tiles to the max level DZI tiles coordinate system. # Place the top-left corner of the center tile range in center of DZI image. offset_x=$(( image_size/SIZE/2 - CENTER_X )) offset_y=$(( image_size/SIZE/2 - CENTER_Y )) #echo "Offset of Tiles in Image : $offset_x, $offset_y" # Offset in the deep zoom image to minecraft world origin center_x=$(( offset_x*SIZE )) # center in DZI image coords center_y=$(( offset_y*SIZE )) # used to convert mouse to world coords #echo "Origin offset of World : $center_x,$center_y" # # Create the top level 'html' file # # FUTURE: if html exists, check center is still the same # rather that simply re-writing the file each time. # if [[ -f "$OUTPUT.html" ]]; then echo "Checking existing \"$OUTPUT.html\""... if grep -q " worldCenter .*Point($center_x,$center_y)" "$OUTPUT.html"; then : "All good -- Existing HTML file as correct settings" else Error "World origin has changed -- Please Delete and rebuild completely" fi else echo "Creating \"$OUTPUT.html\""... echo ' | | | Deepzoom "'"${OUTPUT//_/ }"'" | | | | |
|
| Coords: Click Mouse to Set |
|

Deepzoom "'"${OUTPUT//_/ }"'"

|
| |
| | | | | ' | sed -n '/^ *|/!d; s///; s/^ //; p' > "$OUTPUT.html" fi # # Link the journal map tile images into max level DZI image names # Note these maps are sparse, and dzi format is a sparse pyramid! # There is no need to create any image that has no map components # echo "Linking journeymaps (rename) to deepzoom deepest level ($MAX_MAP_LEVEL)" mkdir -p "${OUTPUT}_files/$MAX_MAP_LEVEL" 2>/dev/null for map in "$MAP_PATH"/*,*.png; do # extract journey map tile coords... read x y <<< $( echo "${map##*/}" | tr -cs '0-9-' ' ' ) if (( LIMIT >= 0 )); then (( x > CENTER_X + LIMIT )) && continue (( y > CENTER_Y + LIMIT )) && continue (( x < CENTER_X - LIMIT )) && continue (( y < CENTER_Y - LIMIT )) && continue fi (( x += offset_x )); (( y += offset_y )) # DZI tile coords # rather than copy, link the journeymap image to max level DZI tile image if [[ ! -e "${OUTPUT}_files/$MAX_MAP_LEVEL/${x}_${y}.png" ]]; then echo " Linking $MAX_MAP_LEVEL/${x}_${y}.png" fi ln -sf $map "${OUTPUT}_files/$MAX_MAP_LEVEL/${x}_${y}.png" done # convert links to be relative, removing any dangling links symlinks -cd "${OUTPUT}_files/$MAX_MAP_LEVEL" >/dev/null # # Merge and shink maps (if map is newer) to lower levels. # # This is a sparse pyramid so all maps need to be looked at. # maps already merged during this run will be newer, so later parts # of the same map do not trigger repeated mergers. # # Level 9 is the level where we are left with 1 image. # So below this no images are merge, just a single image scaled smaller. # I don't think Open Seadragon even uses those images though! # cd "${OUTPUT}_files" for (( level=MAX_MAP_LEVEL; level>$ONE_TILE_LEVEL; level-- )); do new_level=$(( level-1 )) echo "Updating level $new_level" mkdir $new_level 2>/dev/null # make directory images_rebuild= # for each map in this level, see if we need to update the map above. for map in $level/*_*.png; do read x y <<< $( echo "${map##*/}" | tr -cs '0-9-' ' ' ) (( x /= 2 )); y=$(( y /= 2 )); new_map="$new_level/${x}_${y}.png" # the map that it merges into # The $new_map image is a merger of up to 4 maps on current level. # If this needs updating, then update it. Includes up to 3 other maps. if [[ ! -f $new_map || $new_map -ot $map ]]; then echo " Rebuilding \"$new_map\"" # merge all maps involved, if not present use a transparent tile image (( x1=x*2, x2=x1+1, y1=y*2, y2=y1+1 )) m1=$level/${x1}_${y1}.png; [[ -f $m1 ]] || m1="xc:none[x$SIZE]" m2=$level/${x2}_${y1}.png; [[ -f $m2 ]] || m2="xc:none[x$SIZE]" m3=$level/${x1}_${y2}.png; [[ -f $m3 ]] || m3="xc:none[x$SIZE]" m4=$level/${x2}_${y2}.png; [[ -f $m4 ]] || m4="xc:none[x$SIZE]" magick \( "$m1" "$m2" +append \) \ \( "$m3" "$m4" +append \) -append \ -scale x$SIZE +repage \ -background black -depth 8 -quality 95 $new_map [[ $? -ne 0 ]] && Error "Assertion Imagemagick Command failed $?" images_rebuilt=true # flag that images have been rebuilt #else # echo "skipping \"$new_map\"" fi done # Techniqually this is only needed for merged of MAX_MAP_LEVEL # But doesn't hurt. if [[ ! "$images_rebuilt" ]]; then echo "No images were rebuild -- Nothing further to do" exit 0 fi done # # Shrink the single 512x512 image. # # This will likely always be needed if previous loop did not abort. # # NOTE: OpenSeadragon does not appear to even use these images! # But DZI image format requires them for completeness. # for (( level=$ONE_TILE_LEVEL; level>0; level-- )); do new_level=$(( level-1 )) new_size=$(( 2**new_level )) echo "Updating level $new_level" mkdir $new_level 2>/dev/null # make directory map=$level/0_0.png new_map=$new_level/0_0.png echo " Scaling \"$new_map\" to $new_size" magick $map -scale x$new_size +repage \ -background black -depth 8 -quality 95 $new_map [[ $? -ne 0 ]] && Error "Assertion Imagemagick Command failed $?" done # ------