diff --git a/geojson.lua b/geojson.lua new file mode 100644 index 0000000..d76628a --- /dev/null +++ b/geojson.lua @@ -0,0 +1,305 @@ +-- advtrains track map generator +-- Usage: lua main.lua path/to/world + +-- Viewport maximum coordinate in all directions +local maxc = 5000 + +-- embed an image called "world.png" +local wimg = false +-- image file resolution (not world resolution!) +local wimresx = 3000 +local wimresy = 3000 +-- one pixel is ... nodes +local wimscale = 4 + +-- y ranges and line colors +-- Minimum y level drawn +local drminy = -30 +-- Color of min y level +local colminy = {r=0, g=0, b=255} +-- Intermediate color +local colmedy = {r=255, g=0, b=0} +-- Maximum y level drawn +local drmaxy = 80 +-- Color of max y level +local colmaxy = {r=255, g=255, b=0} + + +datapath = (arg[1] or "").."/" + + +--Constant for maximum connection value/division of the circle +AT_CMAX = 16 + +advtrains = {} +minetest = {} +core = minetest + +--table for track nodes/connections +trackconns = {} + +-- math library seems to be missing this function +math.hypot = function(a,b) return math.sqrt(a*a + b*b) end + +-- need to declare this for trackdefs +function attrans(str) return str end + +-- pos to string +local function pts(pos) + return pos.x .. "," .. pos.y .. "," .. pos.z +end + +--Advtrains dump (special treatment of pos and sigd) +function atdump(t, intend) + local str + if type(t)=="table" then + if t.x and t.y and t.z then + str=minetest.pos_to_string(t) + elseif t.p and t.s then -- interlocking sigd + str="S["..minetest.pos_to_string(t.p).."/"..t.s.."]" + else + str="{" + local intd = (intend or "") .. " " + for k,v in pairs(t) do + if type(k)~="string" or not string.match(k, "^path[_]?") then + -- do not print anything path-related + str = str .. "\n" .. intd .. atdump(k, intd) .. " = " ..atdump(v, intd) + end + end + str = str .. "\n" .. (intend or "") .. "}" + end + elseif type(t)=="boolean" then + if t then + str="true" + else + str="false" + end + elseif type(t)=="function" then + str="" + elseif type(t)=="userdata" then + str="" + else + str=""..t + end + return str +end + +dofile("vector.lua") +dofile("serialize.lua") +dofile("helpers.lua") +dofile("tracks.lua") +dofile("track_defs.lua") + +dofile("nodedb.lua") + + +function parse_args(argv) + local i = 1 + local no_trains = false + local datapath, mappath, worldimage + while i <= #argv do + local a = argv[i] + if (a == "-m") or (a == "--map-file") then + -- only draw trains – requires specifying an already drawn file + i = i+1 + if not argv[i] then + error(("missing filename after `%s'"):format(a)) + end + mappath = argv[i] + elseif (a == "-t") or (a == "--no-trains") then + -- do not draw trains + no_trains = true + elseif (a == "-w") or (a == "--world-image") then + -- overlay over world image + i = i+1 + if not argv[i] then + error(("missing filename after `%s'"):format(a)) + end + worldimage = argv[i] + else + datapath = a + end + + i = i + 1 + end + return datapath, mappath, no_trains, worldimage +end + +datapath, mappath, no_trains, worldimage = parse_args(arg) + +-- Load saves +local file, err = io.open(datapath.."advtrains_trains", "r") +local tbl = minetest.deserialize(file:read("*a")) +if type(tbl) ~= "table" then + error("Trains file: not a table") +end +advtrains.trains = tbl +file:close() + +--ndb contains the defs, while ndb2 is the actual contents +file, err = io.open(datapath.."advtrains_ndb", "r") +tbl = minetest.deserialize(file:read("*a")) +if type(tbl) ~= "table" then + error("Node database file: not a table") +end +advtrains.ndb.load_data(tbl) +file:close() + +-- open svg file + +local svgfile = io.open(datapath.."trains.json", "w") + + +if mappath then + mapfile = io.open(mappath, "r") + cont = mapfile:read("*a"):sub(1, -7) -- remove end tag + svgfile:write(cont) +else + svgfile:write([[ +[ +]]) +end + + +-- everything set up. Start generating an SVG +-- All nodes that have been fit into a polyline are removed from the NDB, in order to not draw them again. + +-- "Restart points" for the breadth-first traverser (set when more than 2 conns present) +-- {pos = , connid = , conn = } +-- Note that the node at "pos" is already deleted from the NDB at the time of recall, therefore "conn" is specified +local bfs_rsp = {} + +local ndb_nodes_handled = 0 + +-- Points of the current polyline. Inserted as xyz vectors, maybe we'll use the y value one day +local current_polyline = {} + +-- Traverser function from interlocking, highly modified +local function gen_rsp_polyline(rsp) + + -- trick a bit + local pos, connid, conns = rsp.pos, 1, {rsp.conn} + current_polyline[#current_polyline+1] = pos + + while true do + local adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, conns, connid) + if not adj_pos then + -- proceed one more node, for seamless turnout transitions + current_polyline[#current_polyline+1] = advtrains.pos_add_dir(pos, conns[connid].c) + return + end + -- continue traversing + local conn_mainbranch + for nconnid, nconn in ipairs(next_conns) do + if adj_connid ~= nconnid then + if not conn_mainbranch then + --use the first one found to continue + conn_mainbranch = nconnid + --writec(nconnid.." nconn mainbranch") + else + -- insert bfs reminders for other conns + table.insert(bfs_rsp, {pos = adj_pos, connid = nconnid, conn = nconn}) + --writec(nconnid.." nconn bfs") + end + end + end + + -- save in polyline and delete from ndb + --writec("Saved pos: "..pts(adj_pos).." mainbranch cont "..conn_mainbranch.." nextconns "..atdump(next_conns)) + current_polyline[#current_polyline+1] = adj_pos + advtrains.ndb.clear(adj_pos) + ndb_nodes_handled = ndb_nodes_handled + 1 + + pos, connid, conns = adj_pos, conn_mainbranch, next_conns + + end +end + +plcnt = 0 + +local function poly_angle(l, e) + local dx = (e.x - l.x) + local dz = (e.z - l.z) + return math.deg(math.atan2(dz, dx)) +end + +local function polyline_write(pl) + local str = {} + + table.insert(str, '{"type":"Feature","geometry":{"type":"LineString","coordinates":[') + + local i + local e = nil + local l = nil + local last_angle = -1000 + local lastcf = p1y + local lastldir = {x=0, y=0} + local consider = 0 + --print("new polyline\n") + + for i=1,#pl do + local first = true + + consider = 0 + e = pl[i] + -- print("point " .. e.x .. "x" .. e.z .. "x" .. e.y) + + if (l ~= nil) then + local angle = poly_angle(l, e) + if (angle ~= last_angle) then + table.insert(str, "[".. l.x .. "," .. (l.z) .. "],") + last_angle = angle + end + end + l = e + end + if (e ~= nil) then + table.insert(str, "[".. l.x .. "," .. (l.z) .. "]") + end + table.insert(str, ']},"properties":{"name":""}},\n') + svgfile:write(table.concat(str)) + plcnt = plcnt + 1 +end + + + + +-- while there are entries in the nodedb +-- 1. find a starting point +if not mappath then + local stpos, conns = advtrains.ndb.mapper_find_starting_point() + while stpos do + + --print("Restart at position "..pts(stpos)) + for connid, conn in ipairs(conns) do + table.insert(bfs_rsp, {pos = stpos, connid = connid, conn = conn}) + end + advtrains.ndb.clear(stpos) + + -- 2. while there are BFS entries + while #bfs_rsp > 0 do + -- make polylines + local current_rsp = bfs_rsp[#bfs_rsp] + bfs_rsp[#bfs_rsp] = nil + --print("Starting polyline at "..pts(current_rsp.pos).."/"..current_rsp.connid) + + + current_polyline = {} + + gen_rsp_polyline(current_rsp) + + polyline_write(current_polyline) + + --break + + io.write("Progress ", ndb_nodes_handled, "+", ndb_nodes_notrack, "/", ndb_nodes_total, "=", math.floor(((ndb_nodes_handled+ndb_nodes_notrack)/ndb_nodes_total)*100), "%\r") + end + stpos, conns = advtrains.ndb.mapper_find_starting_point() + --break + end +end + +svgfile:write("{}]") +svgfile:close() + +print("\nWrote",plcnt,"polylines. Processed", ndb_nodes_handled, "track,",ndb_nodes_notrack, "non-track nodes out of", ndb_nodes_total)