// License: GPL. For details, see LICENSE file. package s57; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; import java.util.ListIterator; import s57.S57att.Att; import s57.S57obj.Obj; import s57.S57osm.KeyVal; import s57.S57val.AttVal; /** * @author Malcolm Herring */ public class S57map { // S57/OSM map generation methods // CHECKSTYLE.OFF: LineLength public static class MapBounds { public double minlat; public double minlon; public double maxlat; public double maxlon; public MapBounds() { minlat = Math.toRadians(90); minlon = Math.toRadians(180); maxlat = Math.toRadians(-90); maxlon = Math.toRadians(-180); } } public enum Nflag { ANON, // Edge inner nodes ISOL, // Node not part of Edge CONN, // Edge first and last nodes TRNK, // Edge truncated polygon nodes DPTH // Sounding nodes } public static class Snode { // All coordinates in map public double lat; // Latitude in radians public double lon; // Longitude in radians public Nflag flg; // Role of node public double val; // Optional value public Snode() { flg = Nflag.ANON; lat = 0; lon = 0; val = 0; } public Snode(double ilat, double ilon) { flg = Nflag.ANON; lat = ilat; lon = ilon; val = 0; } public Snode(double ilat, double ilon, Nflag iflg) { lat = ilat; lon = ilon; flg = iflg; val = 0; } public Snode(double ilat, double ilon, double ival) { flg = Nflag.DPTH; lat = ilat; lon = ilon; val = ival; } } public static class Edge { // A polyline segment public long first; // First CONN node public long last; // Last CONN node public ArrayList<Long> nodes; // Inner ANON nodes public Edge() { first = 0; last = 0; nodes = new ArrayList<>(); } } public enum Rflag { UNKN, MASTER, SLAVE } public static class Reln { public long id; public Rflag reln; public Reln(long i, Rflag r) { id = i; reln = r; } } public static class RelTab extends ArrayList<Reln> { public RelTab() { super(); } } public static class ObjTab extends HashMap<Integer, AttMap> { public ObjTab() { super(); } } public static class ObjMap extends EnumMap<Obj, ObjTab> { public ObjMap() { super(Obj.class); } } public static class AttMap extends HashMap<Att, AttVal<?>> { public AttMap() { super(); } } public static class NodeTab extends HashMap<Long, Snode> { public NodeTab() { super(); } } public static class EdgeTab extends HashMap<Long, Edge> { public EdgeTab() { super(); } } public static class FtrMap extends EnumMap<Obj, ArrayList<Feature>> { public FtrMap() { super(Obj.class); } } public static class FtrTab extends HashMap<Long, Feature> { public FtrTab() { super(); } } public static class Prim { // Spatial element public long id; // Snode ID for POINTs, Edge ID for LINEs & AREAs) public boolean forward; // Direction of vector used (LINEs & AREAs) public boolean outer; // Exterior/Interior boundary (AREAs) public boolean trunc; // Cell limit truncation public Prim() { id = 0; forward = true; outer = true; trunc = false; } public Prim(long i) { id = i; forward = true; outer = true; trunc = false; } public Prim(long i, boolean o) { id = i; forward = true; outer = o; trunc = false; } public Prim(long i, boolean f, boolean o) { id = i; forward = f; outer = o; trunc = false; } public Prim(long i, boolean f, boolean o, boolean t) { id = i; forward = f; outer = o; trunc = t; } } public static class Comp { // Composite spatial element public long ref; // ID of Comp public int size; // Number of Prims in this Comp public Comp(long r, int s) { ref = r; size = s; } } public enum Pflag { NOSP, POINT, LINE, AREA } public static class Geom { // Geometric structure of feature public Pflag prim; // Geometry type public ArrayList<Prim> elems; // Ordered list of elements public int outers; // Number of outers public int inners; // Number of inners public ArrayList<Comp> comps; // Ordered list of compounds public double area; // Area of feature public double length; // Length of feature public Snode centre; // Centre of feature public Geom(Pflag p) { prim = p; elems = new ArrayList<>(); outers = inners = 0; comps = new ArrayList<>(); area = 0; length = 0; centre = new Snode(); } } public static class Feature { public long id; // Ref for this feature public Rflag reln; // Relationship status public Geom geom; // Geometry data public Obj type; // Feature type public AttMap atts; // Feature attributes public RelTab rels; // Related objects public ObjMap objs; // Slave object attributes Feature() { id = 0; reln = Rflag.UNKN; geom = new Geom(Pflag.NOSP); type = Obj.UNKOBJ; atts = new AttMap(); rels = new RelTab(); objs = new ObjMap(); } } public MapBounds bounds; public NodeTab nodes; public EdgeTab edges; public FtrMap features; public FtrTab index; public long xref; private long cref; private Feature feature; private Edge edge; private ArrayList<KeyVal<?>> osm; private boolean sea; public S57map(boolean s) { sea = s; nodes = new NodeTab(); // All nodes in map edges = new EdgeTab(); // All edges in map feature = new Feature(); // Current feature being built features = new FtrMap(); // All features in map, grouped by type index = new FtrTab(); // Feature look-up table bounds = new MapBounds(); cref = 0x0000ffffffff0000L; // Compound reference generator xref = 0x0fff000000000000L; // Extras reference generator } // S57 map building methods public void newNode(long id, double lat, double lon, Nflag flag) { nodes.put(id, new Snode(Math.toRadians(lat), Math.toRadians(lon), flag)); if (flag == Nflag.ANON) { edge.nodes.add(id); } } public void newNode(long id, double lat, double lon, double depth) { nodes.put(id, new Snode(Math.toRadians(lat), Math.toRadians(lon), depth)); } public void newFeature(long id, Pflag p, long objl) { feature = new Feature(); Obj obj = S57obj.decodeType(objl); feature.geom = new Geom(p); feature.type = obj; if (obj != Obj.UNKOBJ) { index.put(id, feature); feature.id = id; } } public void refObj(long id, int rind) { Rflag r = Rflag.UNKN; switch (rind) { case 1: r = Rflag.MASTER; break; case 2: r = Rflag.SLAVE; break; case 3: r = Rflag.UNKN; break; } feature.rels.add(new Reln(id, r)); } public void endFeature() { } public void newAtt(long attl, String atvl) { Att att = S57att.decodeAttribute(attl); AttVal<?> val = S57val.decodeValue(atvl, att); feature.atts.put(att, val); } public void newPrim(long id, long ornt, long usag) { feature.geom.elems.add(new Prim(id, (ornt != 2), (usag != 2))); } public void addConn(long id, int topi) { if (topi == 1) { edge.first = id; } else { edge.last = id; } } public void newEdge(long id) { edge = new Edge(); edges.put(id, edge); } public void endFile() { for (long id : index.keySet()) { Feature feature = index.get(id); sortGeom(feature); for (Reln reln : feature.rels) { Feature rel = index.get(reln.id); if (cmpGeoms(feature.geom, rel.geom)) { switch (reln.reln) { case SLAVE: feature.reln = Rflag.MASTER; break; default: feature.reln = Rflag.UNKN; break; } rel.reln = reln.reln; } else { reln.reln = Rflag.UNKN; } } } for (long id : index.keySet()) { Feature feature = index.get(id); if (feature.reln == Rflag.UNKN) { feature.reln = Rflag.MASTER; } if ((feature.type != Obj.UNKOBJ) && (feature.reln == Rflag.MASTER)) { if (features.get(feature.type) == null) { features.put(feature.type, new ArrayList<Feature>()); } features.get(feature.type).add(feature); } } for (long id : index.keySet()) { Feature feature = index.get(id); for (Reln reln : feature.rels) { Feature rel = index.get(reln.id); if (rel.reln == Rflag.SLAVE) { if (feature.objs.get(rel.type) == null) { feature.objs.put(rel.type, new ObjTab()); } ObjTab tab = feature.objs.get(rel.type); int ix = tab.size(); tab.put(ix, rel.atts); } } } } // OSM map building methods public void addNode(long id, double lat, double lon) { nodes.put(id, new Snode(Math.toRadians(lat), Math.toRadians(lon))); feature = new Feature(); feature.id = id; feature.reln = Rflag.UNKN; feature.geom.prim = Pflag.POINT; feature.geom.elems.add(new Prim(id)); edge = null; osm = new ArrayList<>(); } public void addEdge(long id) { feature = new Feature(); feature.id = id; feature.reln = Rflag.UNKN; feature.geom.prim = Pflag.LINE; feature.geom.elems.add(new Prim(id)); edge = new Edge(); osm = new ArrayList<>(); } public void addToEdge(long node) { if (edge.first == 0) { edge.first = node; nodes.get(node).flg = Nflag.CONN; } else { if (edge.last != 0) { edge.nodes.add(edge.last); } edge.last = node; } } public void addArea(long id) { feature = new Feature(); feature.id = id; feature.reln = Rflag.UNKN; feature.geom.prim = Pflag.AREA; edge = null; osm = new ArrayList<>(); } public void addToArea(long id, boolean outer) { feature.geom.elems.add(new Prim(id, outer)); } public void addTag(String key, String val) { feature.reln = Rflag.MASTER; String[] subkeys = key.split(":"); if ((subkeys.length > 1) && subkeys[0].equals("seamark")) { Obj obj = S57obj.enumType(subkeys[1]); if ((subkeys.length > 2) && (obj != Obj.UNKOBJ)) { int idx = 0; Att att = Att.UNKATT; try { idx = Integer.parseInt(subkeys[2]); if (subkeys.length == 4) { att = s57.S57att.enumAttribute(subkeys[3], obj); } } catch (Exception e) { att = S57att.enumAttribute(subkeys[2], obj); } ObjTab objs = feature.objs.get(obj); if (objs == null) { objs = new ObjTab(); feature.objs.put(obj, objs); } AttMap atts = objs.get(idx); if (atts == null) { atts = new AttMap(); objs.put(idx, atts); } AttVal<?> attval = S57val.convertValue(val, att); if (attval.val != null) { if (att == Att.VALSOU) { Snode node = nodes.get(feature.geom.elems.get(0).id); node.val = (Double) attval.val; } atts.put(att, attval); } } else { if (subkeys[1].equals("type")) { obj = S57obj.enumType(val); feature.type = obj; ObjTab objs = feature.objs.get(obj); if (objs == null) { objs = new ObjTab(); feature.objs.put(obj, objs); } AttMap atts = objs.get(0); if (atts == null) { atts = new AttMap(); objs.put(0, atts); } if ((obj == Obj.SOUNDG) && (feature.geom.prim == Pflag.POINT)) { Snode node = nodes.get(feature.geom.elems.get(0).id); node.flg = Nflag.DPTH; } } else { if (obj != Obj.UNKOBJ) { if (val.equals("yes")) { ObjTab objs = feature.objs.get(obj); if (objs == null) { objs = new ObjTab(); feature.objs.put(obj, objs); } } } else { Att att = S57att.enumAttribute(subkeys[1], Obj.UNKOBJ); if (att != Att.UNKATT) { AttVal<?> attval = S57val.convertValue(val, att); if (attval.val != null) feature.atts.put(att, attval); } } } } } else if (!sea) { S57osm.OSMtag(osm, key, val); } } public void tagsDone(long id) { switch (feature.geom.prim) { case POINT: Snode node = nodes.get(id); if ((node.flg != Nflag.CONN) && (node.flg != Nflag.DPTH) && (!feature.objs.isEmpty() || !osm.isEmpty())) { node.flg = Nflag.ISOL; } break; case LINE: edges.put(id, edge); nodes.get(edge.first).flg = Nflag.CONN; nodes.get(edge.last).flg = Nflag.CONN; if (edge.first == edge.last) { feature.geom.prim = Pflag.AREA; } break; case AREA: break; default: break; } if (sortGeom(feature) && !((edge != null) && (edge.last == 0))) { if (feature.type != Obj.UNKOBJ) { index.put(id, feature); if (features.get(feature.type) == null) { features.put(feature.type, new ArrayList<Feature>()); } features.get(feature.type).add(feature); } for (KeyVal<?> kvx : osm) { Feature base = new Feature(); base.reln = Rflag.MASTER; base.geom = feature.geom; base.type = kvx.obj; ObjTab objs = new ObjTab(); base.objs.put(kvx.obj, objs); AttMap atts = new AttMap(); objs.put(0, atts); if (kvx.att != Att.UNKATT) { atts.put(kvx.att, new AttVal<>(kvx.conv, kvx.val)); } index.put(++xref, base); if (features.get(kvx.obj) == null) { features.put(kvx.obj, new ArrayList<Feature>()); } features.get(kvx.obj).add(base); } /* if (!osm.isEmpty()) { if (feature.type == Obj.UNKOBJ) { feature.type = osm.obj; ObjTab objs = feature.objs.get(osm.obj); if (objs == null) { objs = new ObjTab(); feature.objs.put(osm.obj, objs); } AttMap atts = objs.get(0); if (atts == null) { atts = new AttMap(); objs.put(0, atts); } if (osm.att != Att.UNKATT) { atts.put(osm.att, new AttVal<>(osm.conv, osm.val)); } } else { Feature base = new Feature(); base.reln = Rflag.MASTER; base.geom = feature.geom; base.type = osm.obj; ObjTab objs = new ObjTab(); base.objs.put(osm.obj, objs); AttMap atts = new AttMap(); objs.put(0, atts); if (osm.att != Att.UNKATT) { atts.put(osm.att, new AttVal<>(osm.conv, osm.val)); } index.put(++xref, base); if (features.get(osm.obj) == null) { features.put(osm.obj, new ArrayList<Feature>()); } features.get(osm.obj).add(base); } }*/ } } public void mapDone() { if (!sea) { S57box.bBox(this); } } // Utility methods public boolean sortGeom(Feature feature) { try { Geom sort = new Geom(feature.geom.prim); long first = 0; long last = 0; Comp comp = null; boolean next = true; feature.geom.length = 0; feature.geom.area = 0; if (feature.geom.elems.isEmpty()) { return false; } if (feature.geom.prim == Pflag.POINT) { feature.geom.centre = nodes.get(feature.geom.elems.get(0).id); return true; } Geom outer = new Geom(feature.geom.prim); Geom inner = new Geom(feature.geom.prim); for (Prim prim : feature.geom.elems) { if (prim.outer) { outer.elems.add(prim); } else { inner.elems.add(prim); } } boolean outin = true; int sweep = outer.elems.size(); if (sweep == 0) { return false; } int prev = sweep; int top = 0; while (!outer.elems.isEmpty()) { Prim prim = outer.elems.remove(0); Edge edge = edges.get(prim.id); if (edge == null) { return false; } if (next == true) { next = false; first = edge.first; last = edge.last; prim.forward = true; sort.elems.add(prim); if (prim.outer) { sort.outers++; } else { sort.inners++; } comp = new Comp(cref++, 1); sort.comps.add(comp); } else { if (edge.first == last) { sort.elems.add(prim); last = edge.last; prim.forward = true; comp.size++; } else if (edge.last == first) { sort.elems.add(top, prim); first = edge.first; prim.forward = true; comp.size++; } else if (edge.last == last) { sort.elems.add(prim); last = edge.first; prim.forward = false; comp.size++; } else if (edge.first == first) { sort.elems.add(top, prim); first = edge.last; prim.forward = false; comp.size++; } else { outer.elems.add(prim); } } if (--sweep == 0) { sweep = outer.elems.size(); if ((sweep == 0) || (sweep == prev)) { if ((sort.prim == Pflag.AREA) && (first != last)) { return false; } if (outin) { if (sweep != 0) { return false; } outer = inner; outin = false; sweep = outer.elems.size(); } next = true; top = sort.elems.size(); } prev = sweep; } } if ((sort.prim == Pflag.LINE) && (sort.outers == 1) && (sort.inners == 0) && (first == last)) { sort.prim = Pflag.AREA; } feature.geom = sort; if (feature.geom.prim == Pflag.AREA) { int ie = 0; int ic = 0; while (ie < feature.geom.elems.size()) { double area = calcArea(feature.geom, ic); if (ie == 0) feature.geom.area = Math.abs(area) * 3444 * 3444; if (((ie == 0) && (area < 0.0)) || ((ie > 0) && (area >= 0.0))) { ArrayList<Prim> tmp = new ArrayList<>(); for (int i = 0; i < feature.geom.comps.get(ic).size; i++) { Prim p = feature.geom.elems.remove(ie); p.forward = !p.forward; tmp.add(0, p); } feature.geom.elems.addAll(ie, tmp); } ie += feature.geom.comps.get(ic).size; ic++; } } feature.geom.length = calcLength(feature.geom); feature.geom.centre = calcCentroid(feature); return true; } catch (Exception e) { return false; } } public boolean cmpGeoms(Geom g1, Geom g2) { return ((g1.prim == g2.prim) && (g1.outers == g2.outers) && (g1.inners == g2.inners) && (g1.elems.size() == g2.elems.size())); } public class EdgeIterator { Edge edge; boolean forward; ListIterator<Long> it; public EdgeIterator(Edge e, boolean dir) { edge = e; forward = dir; it = null; } public boolean hasNext() { return (edge != null); } public long nextRef() { long ref = 0; if (forward) { if (it == null) { ref = edge.first; it = edge.nodes.listIterator(); } else { if (it.hasNext()) { ref = it.next(); } else { ref = edge.last; edge = null; } } } else { if (it == null) { ref = edge.last; it = edge.nodes.listIterator(edge.nodes.size()); } else { if (it.hasPrevious()) { ref = it.previous(); } else { ref = edge.first; edge = null; } } } return ref; } public Snode next() { return nodes.get(nextRef()); } } public class GeomIterator { Geom geom; Prim prim; EdgeIterator eit; ListIterator<S57map.Prim> ite; ListIterator<Comp> itc; Comp comp; int ec; long lastref; public GeomIterator(Geom g) { geom = g; lastref = 0; ite = geom.elems.listIterator(); itc = geom.comps.listIterator(); } public boolean hasComp() { return (itc.hasNext()); } public long nextComp() { comp = itc.next(); ec = comp.size; lastref = 0; return comp.ref; } public boolean hasEdge() { return (ec > 0) && ite.hasNext(); } public long nextEdge() { prim = ite.next(); eit = new EdgeIterator(edges.get(prim.id), prim.forward); ec--; return prim.id; } public boolean hasNode() { return (eit.hasNext()); } public long nextRef(boolean all) { long ref = eit.nextRef(); if (!all && (ref == lastref)) { ref = eit.nextRef(); } lastref = ref; return ref; } public long nextRef() { return nextRef(false); } public Snode next() { return nodes.get(nextRef()); } } double calcArea(Geom geom, int comp) { Snode node; double lat, lon, llon, llat; lat = lon = llon = llat = 0; double sigma = 0; GeomIterator git = new GeomIterator(geom); for (int i = 0; i <= comp; i++) { if (git.hasComp()) { git.nextComp(); while (git.hasEdge()) { git.nextEdge(); while (git.hasNode()) { node = git.next(); if (node == null) continue; llon = lon; llat = lat; lat = node.lat; lon = node.lon; sigma += (lon * Math.sin(llat)) - (llon * Math.sin(lat)); } } if (i != comp) sigma = lat = lon = llon = llat = 0; } } return sigma / 2.0; } double calcLength(Geom geom) { Snode node; double lat, lon, llon, llat; lat = lon = llon = llat = 0; double sigma = 0; boolean first = true; GeomIterator git = new GeomIterator(geom); while (git.hasComp()) { git.nextComp(); while (git.hasEdge()) { git.nextEdge(); while (git.hasNode()) { node = git.next(); if (first) { first = false; lat = node.lat; lon = node.lon; } else if (node != null) { llat = lat; llon = lon; lat = node.lat; lon = node.lon; sigma += Math.acos(Math.sin(lat) * Math.sin(llat) + Math.cos(lat) * Math.cos(llat) * Math.cos(llon - lon)); } } } } return sigma * 3444; } Snode calcCentroid(Feature feature) { double lat, lon, slat, slon, llat, llon; llat = llon = lat = lon = slat = slon = 0; double sarc = 0; boolean first = true; switch (feature.geom.prim) { case POINT: return nodes.get(feature.geom.elems.get(0).id); case LINE: GeomIterator git = new GeomIterator(feature.geom); while (git.hasComp()) { git.nextComp(); while (git.hasEdge()) { git.nextEdge(); while (git.hasNode()) { Snode node = git.next(); if (node == null) continue; lat = node.lat; lon = node.lon; if (first) { first = false; } else { sarc += (Math.acos(Math.cos(lon - llon) * Math.cos(lat - llat))); } llat = lat; llon = lon; } } } double harc = sarc / 2; sarc = 0; first = true; git = new GeomIterator(feature.geom); while (git.hasComp()) { git.nextComp(); while (git.hasEdge()) { git.nextEdge(); while (git.hasNode()) { Snode node = git.next(); if (node == null) continue; lat = node.lat; lon = node.lon; if (first) { first = false; } else { sarc = (Math.acos(Math.cos(lon - llon) * Math.cos(lat - llat))); if (sarc > harc) break; } harc -= sarc; llat = lat; llon = lon; } } } return new Snode(llat + ((lat - llat) * harc / sarc), llon + ((lon - llon) * harc / sarc)); case AREA: git = new GeomIterator(feature.geom); while (git.hasComp()) { git.nextComp(); while (git.hasEdge()) { git.nextEdge(); while (git.hasNode()) { Snode node = git.next(); lat = node.lat; lon = node.lon; if (first) { first = false; } else { double arc = (Math.acos(Math.cos(lon - llon) * Math.cos(lat - llat))); slat += ((lat + llat) / 2 * arc); slon += ((lon + llon) / 2 * arc); sarc += arc; } llon = lon; llat = lat; } } } return new Snode((sarc > 0.0 ? slat / sarc : 0.0), (sarc > 0.0 ? slon / sarc : 0.0)); default: } return null; } }