001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.mappaint.xml; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.io.InputStream; 007 import java.io.InputStreamReader; 008 import java.io.IOException; 009 import java.util.Collection; 010 import java.util.Collections; 011 import java.util.HashMap; 012 import java.util.LinkedList; 013 import java.util.List; 014 015 import org.openstreetmap.josm.Main; 016 import org.openstreetmap.josm.data.osm.Node; 017 import org.openstreetmap.josm.data.osm.OsmPrimitive; 018 import org.openstreetmap.josm.data.osm.OsmUtils; 019 import org.openstreetmap.josm.data.osm.Relation; 020 import org.openstreetmap.josm.data.osm.Way; 021 import org.openstreetmap.josm.gui.mappaint.Cascade; 022 import org.openstreetmap.josm.gui.mappaint.Keyword; 023 import org.openstreetmap.josm.gui.mappaint.MultiCascade; 024 import org.openstreetmap.josm.gui.mappaint.Range; 025 import org.openstreetmap.josm.gui.mappaint.StyleKeys; 026 import org.openstreetmap.josm.gui.mappaint.StyleSource; 027 import org.openstreetmap.josm.gui.preferences.SourceEntry; 028 import org.openstreetmap.josm.io.MirroredInputStream; 029 import org.openstreetmap.josm.tools.Utils; 030 import org.openstreetmap.josm.tools.XmlObjectParser; 031 import org.xml.sax.SAXException; 032 import org.xml.sax.SAXParseException; 033 034 public class XmlStyleSource extends StyleSource implements StyleKeys { 035 036 protected final HashMap<String, IconPrototype> icons = new HashMap<String, IconPrototype>(); 037 protected final HashMap<String, LinePrototype> lines = new HashMap<String, LinePrototype>(); 038 protected final HashMap<String, LinemodPrototype> modifiers = new HashMap<String, LinemodPrototype>(); 039 protected final HashMap<String, AreaPrototype> areas = new HashMap<String, AreaPrototype>(); 040 protected final LinkedList<IconPrototype> iconsList = new LinkedList<IconPrototype>(); 041 protected final LinkedList<LinePrototype> linesList = new LinkedList<LinePrototype>(); 042 protected final LinkedList<LinemodPrototype> modifiersList = new LinkedList<LinemodPrototype>(); 043 protected final LinkedList<AreaPrototype> areasList = new LinkedList<AreaPrototype>(); 044 045 public XmlStyleSource(String url, String name, String shortdescription) { 046 super(url, name, shortdescription); 047 } 048 049 public XmlStyleSource(SourceEntry entry) { 050 super(entry); 051 } 052 053 protected void init() { 054 super.init(); 055 icons.clear(); 056 lines.clear(); 057 modifiers.clear(); 058 areas.clear(); 059 iconsList.clear(); 060 linesList.clear(); 061 modifiersList.clear(); 062 areasList.clear(); 063 } 064 065 @Override 066 public void loadStyleSource() { 067 init(); 068 try { 069 InputStreamReader reader = new InputStreamReader(getSourceInputStream()); 070 XmlObjectParser parser = new XmlObjectParser(new XmlStyleSourceHandler(this)); 071 parser.startWithValidation(reader, 072 "http://josm.openstreetmap.de/mappaint-style-1.0", 073 "resource://data/mappaint-style.xsd"); 074 while(parser.hasNext()) { 075 } 076 077 } catch(IOException e) { 078 System.err.println(tr("Warning: failed to load Mappaint styles from ''{0}''. Exception was: {1}", url, e.toString())); 079 e.printStackTrace(); 080 logError(e); 081 } catch(SAXParseException e) { 082 System.err.println(tr("Warning: failed to parse Mappaint styles from ''{0}''. Error was: [{1}:{2}] {3}", url, e.getLineNumber(), e.getColumnNumber(), e.getMessage())); 083 e.printStackTrace(); 084 logError(e); 085 } catch(SAXException e) { 086 System.err.println(tr("Warning: failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage())); 087 e.printStackTrace(); 088 logError(e); 089 } 090 } 091 092 public InputStream getSourceInputStream() throws IOException { 093 MirroredInputStream in = new MirroredInputStream(url); 094 InputStream zip = in.getZipEntry("xml", "style"); 095 if (zip != null) { 096 zipIcons = in.getFile(); 097 return zip; 098 } else { 099 zipIcons = null; 100 return in; 101 } 102 } 103 104 private static class WayPrototypesRecord { 105 public LinePrototype line; 106 public List<LinemodPrototype> linemods; 107 public AreaPrototype area; 108 } 109 110 private <T extends Prototype> T update(T current, T candidate, Double scale, MultiCascade mc) { 111 return requiresUpdate(current, candidate, scale, mc) ? candidate : current; 112 } 113 114 /** 115 * checks whether a certain match is better than the current match 116 * @param current can be null 117 * @param candidate the new Prototype that could be used instead 118 * @param scale ignored if null, otherwise checks if scale is within the range of candidate 119 * @param mc side effect: update the valid region for the current MultiCascade 120 */ 121 private boolean requiresUpdate(Prototype current, Prototype candidate, Double scale, MultiCascade mc) { 122 if (current == null || candidate.priority >= current.priority) { 123 if (scale == null) 124 return true; 125 126 if (candidate.range.contains(scale)) { 127 mc.range = Range.cut(mc.range, candidate.range); 128 return true; 129 } else { 130 mc.range = mc.range.reduceAround(scale, candidate.range); 131 return false; 132 } 133 } 134 return false; 135 } 136 137 private IconPrototype getNode(OsmPrimitive primitive, Double scale, MultiCascade mc) { 138 IconPrototype icon = null; 139 for (String key : primitive.keySet()) { 140 String val = primitive.get(key); 141 IconPrototype p; 142 if ((p = icons.get("n" + key + "=" + val)) != null) { 143 icon = update(icon, p, scale, mc); 144 } 145 if ((p = icons.get("b" + key + "=" + OsmUtils.getNamedOsmBoolean(val))) != null) { 146 icon = update(icon, p, scale, mc); 147 } 148 if ((p = icons.get("x" + key)) != null) { 149 icon = update(icon, p, scale, mc); 150 } 151 } 152 for (IconPrototype s : iconsList) { 153 if (s.check(primitive)) 154 { 155 icon = update(icon, s, scale, mc); 156 } 157 } 158 return icon; 159 } 160 161 /** 162 * @param closed The primitive is a closed way or we pretend it is closed. 163 * This is useful for multipolygon relations and outer ways of untagged 164 * multipolygon relations. 165 */ 166 private void get(OsmPrimitive primitive, boolean closed, WayPrototypesRecord p, Double scale, MultiCascade mc) { 167 String lineIdx = null; 168 HashMap<String, LinemodPrototype> overlayMap = new HashMap<String, LinemodPrototype>(); 169 boolean isNotArea = OsmUtils.isFalse(primitive.get("area")); 170 for (String key : primitive.keySet()) { 171 String val = primitive.get(key); 172 AreaPrototype styleArea; 173 LinePrototype styleLine; 174 LinemodPrototype styleLinemod; 175 String idx = "n" + key + "=" + val; 176 if ((styleArea = areas.get(idx)) != null && (closed || !styleArea.closed) && !isNotArea) { 177 p.area = update(p.area, styleArea, scale, mc); 178 } 179 if ((styleLine = lines.get(idx)) != null) { 180 if (requiresUpdate(p.line, styleLine, scale, mc)) { 181 p.line = styleLine; 182 lineIdx = idx; 183 } 184 } 185 if ((styleLinemod = modifiers.get(idx)) != null) { 186 if (requiresUpdate(null, styleLinemod, scale, mc)) { 187 overlayMap.put(idx, styleLinemod); 188 } 189 } 190 idx = "b" + key + "=" + OsmUtils.getNamedOsmBoolean(val); 191 if ((styleArea = areas.get(idx)) != null && (closed || !styleArea.closed) && !isNotArea) { 192 p.area = update(p.area, styleArea, scale, mc); 193 } 194 if ((styleLine = lines.get(idx)) != null) { 195 if (requiresUpdate(p.line, styleLine, scale, mc)) { 196 p.line = styleLine; 197 lineIdx = idx; 198 } 199 } 200 if ((styleLinemod = modifiers.get(idx)) != null) { 201 if (requiresUpdate(null, styleLinemod, scale, mc)) { 202 overlayMap.put(idx, styleLinemod); 203 } 204 } 205 idx = "x" + key; 206 if ((styleArea = areas.get(idx)) != null && (closed || !styleArea.closed) && !isNotArea) { 207 p.area = update(p.area, styleArea, scale, mc); 208 } 209 if ((styleLine = lines.get(idx)) != null) { 210 if (requiresUpdate(p.line, styleLine, scale, mc)) { 211 p.line = styleLine; 212 lineIdx = idx; 213 } 214 } 215 if ((styleLinemod = modifiers.get(idx)) != null) { 216 if (requiresUpdate(null, styleLinemod, scale, mc)) { 217 overlayMap.put(idx, styleLinemod); 218 } 219 } 220 } 221 for (AreaPrototype s : areasList) { 222 if ((closed || !s.closed) && !isNotArea && s.check(primitive)) { 223 p.area = update(p.area, s, scale, mc); 224 } 225 } 226 for (LinePrototype s : linesList) { 227 if (s.check(primitive)) { 228 p.line = update(p.line, s, scale, mc); 229 } 230 } 231 for (LinemodPrototype s : modifiersList) { 232 if (s.check(primitive)) { 233 if (requiresUpdate(null, s, scale, mc)) { 234 overlayMap.put(s.getCode(), s); 235 } 236 } 237 } 238 overlayMap.remove(lineIdx); // do not use overlay if linestyle is from the same rule (example: railway=tram) 239 if (!overlayMap.isEmpty()) { 240 List<LinemodPrototype> tmp = new LinkedList<LinemodPrototype>(); 241 if (p.linemods != null) { 242 tmp.addAll(p.linemods); 243 } 244 tmp.addAll(overlayMap.values()); 245 Collections.sort(tmp); 246 p.linemods = tmp; 247 } 248 } 249 250 public void add(XmlCondition c, Collection<XmlCondition> conditions, Prototype prot) { 251 if(conditions != null) 252 { 253 prot.conditions = conditions; 254 if (prot instanceof IconPrototype) { 255 iconsList.add((IconPrototype) prot); 256 } else if (prot instanceof LinemodPrototype) { 257 modifiersList.add((LinemodPrototype) prot); 258 } else if (prot instanceof LinePrototype) { 259 linesList.add((LinePrototype) prot); 260 } else if (prot instanceof AreaPrototype) { 261 areasList.add((AreaPrototype) prot); 262 } else 263 throw new RuntimeException(); 264 } 265 else { 266 String key = c.getKey(); 267 prot.code = key; 268 if (prot instanceof IconPrototype) { 269 icons.put(key, (IconPrototype) prot); 270 } else if (prot instanceof LinemodPrototype) { 271 modifiers.put(key, (LinemodPrototype) prot); 272 } else if (prot instanceof LinePrototype) { 273 lines.put(key, (LinePrototype) prot); 274 } else if (prot instanceof AreaPrototype) { 275 areas.put(key, (AreaPrototype) prot); 276 } else 277 throw new RuntimeException(); 278 } 279 } 280 281 @Override 282 public void apply(MultiCascade mc, OsmPrimitive osm, double scale, OsmPrimitive multipolyOuterWay, boolean pretendWayIsClosed) { 283 Cascade def = mc.getOrCreateCascade("default"); 284 boolean useMinMaxScale = Main.pref.getBoolean("mappaint.zoomLevelDisplay", false); 285 286 if (osm instanceof Node || (osm instanceof Relation && "restriction".equals(osm.get("type")))) { 287 IconPrototype icon = getNode(osm, (useMinMaxScale ? scale : null), mc); 288 if (icon != null) { 289 def.put(ICON_IMAGE, icon.icon); 290 if (osm instanceof Node) { 291 if (icon.annotate != null) { 292 if (icon.annotate) { 293 def.put(TEXT, Keyword.AUTO); 294 } else { 295 def.remove(TEXT); 296 } 297 } 298 } 299 } 300 } else if (osm instanceof Way || (osm instanceof Relation && ((Relation)osm).isMultipolygon())) { 301 WayPrototypesRecord p = new WayPrototypesRecord(); 302 get(osm, pretendWayIsClosed || !(osm instanceof Way) || ((Way) osm).isClosed(), p, (useMinMaxScale ? scale : null), mc); 303 if (p.line != null) { 304 def.put(WIDTH, new Float(p.line.getWidth())); 305 def.putOrClear(REAL_WIDTH, p.line.realWidth != null ? new Float(p.line.realWidth) : null); 306 def.putOrClear(COLOR, p.line.color); 307 if (p.line.color != null) { 308 int alpha = p.line.color.getAlpha(); 309 if (alpha != 255) { 310 def.put(OPACITY, Utils.color_int2float(alpha)); 311 } 312 } 313 def.putOrClear(DASHES, p.line.getDashed()); 314 def.putOrClear(DASHES_BACKGROUND_COLOR, p.line.dashedColor); 315 } 316 Float refWidth = def.get(WIDTH, null, Float.class); 317 if (refWidth != null && p.linemods != null) { 318 int numOver = 0, numUnder = 0; 319 320 while (mc.hasLayer(String.format("over_%d", ++numOver))) {} 321 while (mc.hasLayer(String.format("under_%d", ++numUnder))) {} 322 323 for (LinemodPrototype mod : p.linemods) { 324 Cascade c; 325 if (mod.over) { 326 String layer = String.format("over_%d", numOver); 327 c = mc.getOrCreateCascade(layer); 328 c.put(OBJECT_Z_INDEX, new Float(numOver)); 329 ++numOver; 330 } else { 331 String layer = String.format("under_%d", numUnder); 332 c = mc.getOrCreateCascade(layer); 333 c.put(OBJECT_Z_INDEX, new Float(-numUnder)); 334 ++numUnder; 335 } 336 c.put(WIDTH, new Float(mod.getWidth(refWidth))); 337 c.putOrClear(COLOR, mod.color); 338 if (mod.color != null) { 339 int alpha = mod.color.getAlpha(); 340 if (alpha != 255) { 341 c.put(OPACITY, Utils.color_int2float(alpha)); 342 } 343 } 344 c.putOrClear(DASHES, mod.getDashed()); 345 c.putOrClear(DASHES_BACKGROUND_COLOR, mod.dashedColor); 346 } 347 } 348 if (multipolyOuterWay != null) { 349 WayPrototypesRecord p2 = new WayPrototypesRecord(); 350 get(multipolyOuterWay, true, p2, (useMinMaxScale ? scale : null), mc); 351 if (Utils.equal(p.area, p2.area)) { 352 p.area = null; 353 } 354 } 355 if (p.area != null) { 356 def.putOrClear(FILL_COLOR, p.area.color); 357 def.putOrClear(TEXT_POSITION, Keyword.CENTER); 358 def.putOrClear(TEXT, Keyword.AUTO); 359 def.remove(FILL_IMAGE); 360 } 361 } 362 } 363 364 }