package net.osmand.osm.util; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.swing.JFrame; import javax.swing.JMenuBar; import javax.swing.UIManager; import javax.xml.stream.XMLStreamException; import net.osmand.Algoritms; import net.osmand.data.DataTileManager; import net.osmand.impl.ConsoleProgressImplementation; import net.osmand.osm.Entity; import net.osmand.osm.EntityInfo; import net.osmand.osm.LatLon; import net.osmand.osm.MapUtils; import net.osmand.osm.Node; import net.osmand.osm.OpeningHoursParser; import net.osmand.osm.Entity.EntityId; import net.osmand.osm.OpeningHoursParser.BasicDayOpeningHourRule; import net.osmand.osm.OpeningHoursParser.OpeningHoursRule; import net.osmand.osm.io.IOsmStorageFilter; import net.osmand.osm.io.OsmBaseStorage; import net.osmand.osm.io.OsmStorageWriter; import net.osmand.swing.DataExtractionSettings; import net.osmand.swing.MapPanel; import net.osmand.swing.MapPointsLayer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.xml.sax.SAXException; /** * Downloads list of Albert Heijn supermarkets and converts them to a map. * * Source for json is: * http://www.ah.nl/albertheijn/winkelinformatie/winkels * * @author Andrei Adamian * */ public class AHSupermarketResolver { private Log log = LogFactory.getLog(AHSupermarketResolver.class); public Map<String, Map<String, Object>> getSupermarkets() throws IOException { InputStream stream = openStream(); try { JSONArray object = new JSONArray(new JSONTokener(new InputStreamReader(stream))); return parseJSON(object); } catch (JSONException t) { throw new IllegalStateException("Can't read json stream",t); } finally { try { stream.close(); } catch (IOException e) { } } } @SuppressWarnings("unchecked") private Map<String, Map<String, Object>> parseJSON(JSONArray array) { HashMap<String,Map<String, Object>> map = new LinkedHashMap<String, Map<String, Object>>(); for (int i=0;i<array.length();i++) { try { JSONObject object = (JSONObject) array.get(i); String id = object.getString("no"); if (id != null) { Iterator<String> keys = object.keys(); Map<String, Object> properties = new LinkedHashMap<String, Object>(); while (keys.hasNext()) { String name = keys.next(); properties.put(name, object.get(name)); } map.put(id, properties); } else { log.warn(String.format("Shop entry '%s' has been skipped, because 'no' property was not found", object)); } } catch (JSONException e) { log.warn("Can't get object at index "+i,e); } } return map; } protected InputStream openStream() throws IOException { URL url = new URL("http://www.ah.nl/albertheijn/winkelinformatie/winkels"); return url.openStream(); } public static void selfTest() throws IOException { final String json = "[{city:'Eindhoven',lat:51.443278,format:'TOGO',lng:5.480161,sunday:true,street:'Neckerspoel',hours:[{F:'0630',U:'2300',D:'09-08-2010'},{F:'0630',U:'2300',D:'10-08-2010'},{F:'0630',U:'2300',D:'11-08-2010'},{F:'0630',U:'2300',D:'12-08-2010'},{F:'0630',U:'2330',D:'13-08-2010'},{F:'0700',U:'2330',D:'14-08-2010'},{F:'0800',U:'2300',D:'15-08-2010'},{F:'0630',U:'2300',D:'16-08-2010'},{F:'0630',U:'2300',D:'17-08-2010'},{F:'0630',U:'2300',D:'18-08-2010'},{F:'0630',U:'2300',D:'19-08-2010'},{F:'0630',U:'2330',D:'20-08-2010'},{F:'0700',U:'2330',D:'21-08-2010'},{F:'0800',U:'2300',D:'22-08-2010'}],no:5816,phone:'040-2376060',zip:'5611 AD',housenr:'10'},{city:'Amsterdam',lat:52.346837,format:'TOGO',lng:4.918422,sunday:true,street:'Julianaplein',hours:[{F:'0630',U:'2359',D:'09-08-2010'},{F:'0630',U:'2359',D:'10-08-2010'},{F:'0630',U:'2359',D:'11-08-2010'},{F:'0630',U:'2359',D:'12-08-2010'},{F:'0630',U:'2359',D:'13-08-2010'},{F:'0700',U:'2359',D:'14-08-2010'},{F:'0900',U:'2359',D:'15-08-2010'},{F:'0630',U:'2359',D:'16-08-2010'},{F:'0630',U:'2359',D:'17-08-2010'},{F:'0630',U:'2359',D:'18-08-2010'},{F:'0630',U:'2359',D:'19-08-2010'},{F:'0630',U:'2359',D:'20-08-2010'},{F:'0700',U:'2359',D:'21-08-2010'},{F:'0900',U:'2359',D:'22-08-2010'}],no:5817,phone:'020-4689944',zip:'1097 DN',housenr:'1'}]"; AHSupermarketResolver resolver = new AHSupermarketResolver() { protected InputStream openStream() throws IOException { return new ByteArrayInputStream(json.getBytes()); } }; Map<String, Map<String, Object>> map = resolver.getSupermarkets(); assert map.size() == 2; assert map.containsKey("5816"); assert map.containsKey("5817"); // further checks Map<String, Object> shop = map.get("5816"); assert "Eindhoven".equals(shop.get("city")); shop = map.get("5817"); assert "Amsterdam".equals(shop.get("city")); } // this file could be retrieved using xapi // http://xapi.openstreetmap.org/api/0.6/*[shop=supermarket][bbox=2.5,50,7.8,53.5] public void updateOSMFile(String pathToOsmFile, String pathToModifiedFile, boolean show) throws IOException, SAXException, XMLStreamException, JSONException{ OsmBaseStorage storage = new OsmBaseStorage(); final Map<String, EntityId> winkelNumbers = new LinkedHashMap<String, EntityId>(); storage.getFilters().add(new IOsmStorageFilter(){ @Override public boolean acceptEntityToLoad(OsmBaseStorage storage, EntityId entityId, Entity entity) { if(entity.getTag("winkelnummer") !=null && entity.getTag("name").contains("eijn")){ winkelNumbers.put(entity.getTag("winkelnummer"), entityId); return true; } // register all nodes in order to operate with ways return true; } }); storage.parseOSM(new FileInputStream(pathToOsmFile), new ConsoleProgressImplementation(2), null, true); Map<String, Map<String, Object>> supermarkets = getSupermarkets(); DataTileManager<Entity> deleted = new DataTileManager<Entity>(); for(String s : winkelNumbers.keySet()){ if(!supermarkets.containsKey(s)){ System.err.println("Shop " + s + " id=" +winkelNumbers.get(s) + " doesn't present on the site."); EntityId e = winkelNumbers.get(s); Entity en = storage.getRegisteredEntities().get(e); deleted.registerObject(en.getLatLon().getLatitude(), en.getLatLon().getLongitude(), en); } } DataTileManager<Entity> notCorrelated = new DataTileManager<Entity>(); DataTileManager<Entity> notShown = new DataTileManager<Entity>(); for(String s : supermarkets.keySet()){ Map<String, Object> props = supermarkets.get(s); if(winkelNumbers.get(s) != null){ EntityId id = winkelNumbers.get(s); Entity e = storage.getRegisteredEntities().get(id); EntityInfo info = storage.getRegisteredEntityInfo().get(id); Map<String, String> newTags = new LinkedHashMap<String, String>(); String p = props.get("format")+""; //IMPORTANT : comment what information should be updated or check String name = "Albert Heijn"; if(!p.equals("AH")){ name += " " + p; } newTags.put("name", name); newTags.put("phone", props.get("phone")+""); newTags.put("addr:city", props.get("city")+""); newTags.put("addr:street", props.get("street")+""); newTags.put("addr:housenumber", props.get("housenr")+""); newTags.put("addr:postcode", props.get("zip")+""); JSONArray o = (JSONArray) props.get("hours"); List<OpeningHoursParser.OpeningHoursRule> rules = new ArrayList<OpeningHoursRule>(); BasicDayOpeningHourRule prev = null; for(int i=0; i<7; i++){ JSONObject obj = o.getJSONObject(i); if(!obj.isNull("C") && obj.getBoolean("C")){ } else { String opened = obj.get("F")+""; String closed = obj.get("U")+""; int start = Integer.parseInt(opened.substring(0, 2)) * 60 + Integer.parseInt(opened.substring(2)); int end = Integer.parseInt(closed.substring(0, 2)) * 60 + Integer.parseInt(closed.substring(2)); if(prev != null && prev.getStartTime() == start && prev.getEndTime() == end){ prev.getDays()[i] = true; } else { BasicDayOpeningHourRule rule = new OpeningHoursParser.BasicDayOpeningHourRule(); rule.getDays()[i] = true; rule.setStartTime(start); rule.setEndTime(end); prev = rule; rules.add(rule); } } } newTags.put("opening_hours", OpeningHoursParser.toStringOpenedHours(rules)); // Check distance to info LatLon real = new LatLon((Double)props.get("lat"), (Double) props.get("lng")); double dist = MapUtils.getDistance(e.getLatLon(), real); if(dist > 150){ // TODO move shop ? System.err.println("Winkel number = " + s + " is too far from site info - " + dist + " m !!! " + real); if(dist > 300){ notCorrelated.registerObject(real.getLatitude(), real.getLongitude(), e); } } boolean changed = false; for(String k : newTags.keySet()){ String val = newTags.get(k); if(!Algoritms.objectEquals(val, e.getTag(k))){ e.putTag(k, val); changed = true; } } if(changed){ info.setAction("modify"); } } else { // TODO? LatLon real = new LatLon((Double)props.get("lat"), (Double) props.get("lng")); System.err.println("Winkel number = " + s + " is not found in database !!! " + real); Node n = new Node(real.getLatitude(), real.getLongitude(), -1); n.putTag("winkelnummer", "REG : " + s); notShown.registerObject(real.getLatitude(), real.getLongitude(), n); } } OsmStorageWriter writer = new OsmStorageWriter(); writer.saveStorage(new FileOutputStream(pathToModifiedFile), storage, null, true); if(show){ try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace(); } final MapPanel panel = new MapPanel(DataExtractionSettings.getSettings().getTilesDirectory()); panel.setFocusable(true); MapPointsLayer toAdd = panel.getLayer(MapPointsLayer.class); toAdd.setPoints(notShown); toAdd.setPointSize(5); toAdd.setTagToShow("winkelnummer"); MapPointsLayer red = new MapPointsLayer(); red.setPoints(deleted); red.setColor(Color.red); red.setPointSize(5); panel.addLayer(red); MapPointsLayer blue = new MapPointsLayer(); blue.setPoints(notCorrelated); blue.setColor(Color.blue); blue.setPointSize(4); panel.addLayer(blue); JFrame frame = new JFrame("Map view"); frame.addWindowListener(new WindowAdapter(){ @Override public void windowClosing(WindowEvent e) { DataExtractionSettings settings = DataExtractionSettings.getSettings(); settings.saveDefaultLocation(panel.getLatitude(), panel.getLongitude()); settings.saveDefaultZoom(panel.getZoom()); System.exit(0); } }); Container content = frame.getContentPane(); content.add(panel, BorderLayout.CENTER); JMenuBar bar = new JMenuBar(); bar.add(MapPanel.getMenuToChooseSource(panel)); frame.setJMenuBar(bar); frame.setSize(512, 512); frame.setVisible(true); } } public static void main(String[] args) throws IOException, SAXException, XMLStreamException, JSONException { AHSupermarketResolver resolver = new AHSupermarketResolver(); resolver.updateOSMFile("e:/Information/OSM maps/osm_map/holl_supermarket.osm", "e:/Information/OSM maps/osm_map/ams_poi_mod.osm", true); } }