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