Package aimax.osm.data.impl

Source Code of aimax.osm.data.impl.DefaultMap

package aimax.osm.data.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.logging.Logger;

import aimax.osm.data.BoundingBox;
import aimax.osm.data.EntityAttributeManager;
import aimax.osm.data.EntityClassifier;
import aimax.osm.data.EntityFinder;
import aimax.osm.data.EntityVisitor;
import aimax.osm.data.MapBuilder;
import aimax.osm.data.MapEvent;
import aimax.osm.data.MapEventListener;
import aimax.osm.data.MapWayFilter;
import aimax.osm.data.OsmMap;
import aimax.osm.data.Position;
import aimax.osm.data.entities.EntityAttribute;
import aimax.osm.data.entities.EntityViewInfo;
import aimax.osm.data.entities.MapEntity;
import aimax.osm.data.entities.MapNode;
import aimax.osm.data.entities.MapWay;
import aimax.osm.data.entities.Track;

/**
* Central container for OSM map data. It is responsible for storing loaded map
* data and also for data preparation to support efficient routing and map
* viewing. All map data is kept in RAM in this implementation. Data preparation
* is based on two fundamental tree structures:
*
* <p>
* The first is an entity classifier. It is used to attach viewing information
* to map entities based on attribute value checks. Renderers can store whatever
* they need in those view information objects. However, the details are not
* visible to this layer. The data storage only sees the minimal scale in which
* the entity shall be visible.
* </p>
*
* <p>
* The second is a kd-tree (see {@link aimax.osm.data.impl.KDTree}).
* </p>
*
* <p>
* The map data storage is used as model for the viewer.
* </p>
*
* @author Ruediger Lunde
*/
public class DefaultMap implements OsmMap {
  private static Logger LOG = Logger.getLogger("aimax.osm");
  private BoundingBox boundingBox;
  /**
   * Maintains all map nodes during map loading; after compilation only the
   * way nodes remain. IDs are used as keys.
   */
  private Hashtable<Long, MapNode> nodes;
  /** Maintains all way nodes. IDs are used as keys. */
  private Hashtable<Long, MapWay> ways;
  /**
   * Maintains after compilation all nodes which have a name or at least one
   * attribute.
   */
  private ArrayList<MapNode> pois;
  /** Maintains markers (not part of the original map). */
  private ArrayList<MapNode> markers;
  /** Maintains tracks (not part of the original map). */
  private ArrayList<Track> tracks;
  private long nextTrackId;

  private EntityClassifier<EntityViewInfo> entityClassifier;
  private KDTree entityTree;

  private ArrayList<MapEventListener> listeners;

  public DefaultMap() {
    nodes = new Hashtable<Long, MapNode>();
    ways = new Hashtable<Long, MapWay>();
    pois = new ArrayList<MapNode>();
    markers = new ArrayList<MapNode>();
    tracks = new ArrayList<Track>();
    listeners = new ArrayList<MapEventListener>();
    // EntityAttributeManager.instance().ignoreAttKeys(new String[]{
    // "created_by", "source", "history", "copyright", "fire_hydrant"},
    // true);
    EntityAttributeManager.instance()
        .ignoreAttKeys(
            new String[] { "created_by", "source", "history",
                "copyright" }, false);
  }

  /** No data available after this reset. */
  protected void clear() {
    EntityAttributeManager.instance().clearHash();
    nodes.clear();
    ways.clear();
    pois.clear();
    markers.clear();
    tracks.clear();
    entityTree = null;
    boundingBox = null;
    fireMapDataEvent(new MapEvent(this, MapEvent.Type.MAP_CLEARED));
  }

  /** {@inheritDoc} */
  @Override
  public boolean isEmpty() {
    return nodes.isEmpty() && ways.isEmpty() && pois.isEmpty()
        && markers.isEmpty() && tracks.isEmpty();
  }

  /** {@inheritDoc} */
  @Override
  public MapBuilder getBuilder() {
    return new DefaultMapBuilder(this);
  }

  /** Does nothing */
  @Override
  public void close() {
  }

  /**
   * Provides the data store with an entity classifier. The classifier
   * strongly influences the generation of the entity tree.
   */
  @Override
  public void setEntityClassifier(EntityClassifier<EntityViewInfo> classifier) {
    entityClassifier = classifier;
    if (entityTree != null) {
      applyClassifierAndUpdateTree(entityTree.getBoundingBox());
      fireMapDataEvent(new MapEvent(this, MapEvent.Type.MAP_MODIFIED));
    }
  }

  /** {@inheritDoc} */
  @Override
  public BoundingBox getBoundingBox() {
    return boundingBox;
  }

  /** Defines a bounding box for the map. */
  protected void setBoundingBox(BoundingBox bb) {
    boundingBox = bb;
  }

  /** {@inheritDoc} */
  @Override
  public int getNodeCount() {
    return nodes.size();
  }

  /** {@inheritDoc} */
  @Override
  public MapNode getNode(long id) {
    return nodes.get(id);
  }

  /** Adds a node to the central node hashtable. */
  protected void addNode(DefaultMapNode node) {
    nodes.put(node.getId(), node);
  }

  /** {@inheritDoc} */
  @Override
  public int getWayCount() {
    return ways.size();
  }

  /** {@inheritDoc} */
  @Override
  public MapWay getWay(long id) {
    return ways.get(id);
  }

  /** Adds a way to the central way hashtable. */
  protected void addWay(DefaultMapWay way) {
    ways.put(way.getId(), way);
  }

  /**
   * Returns all map ways which intersect the specified bounding box.
   */
  @Override
  public Collection<MapWay> getWays(BoundingBox bb) {
    // not really efficient but OK for small maps...
    List<MapWay> result = new ArrayList<MapWay>();
    for (MapWay way : ways.values())
      if (way.computeBoundingBox().intersectsWith(bb))
        result.add(way);
    return result;
  }

  /**
   * Separates way nodes from points of interests, cleans up useless garbage
   * and creates a kd-tree for the remaining entities. Always call this method
   * before using using the container for viewing.
   */
  public void compile() {
    ArrayList<Long> toDelete = new ArrayList<Long>();
    for (MapNode node : nodes.values()) {
      if (node.hasPosition()) {
        if (node.getName() != null || node.getAttributes().length > 0)
          pois.add(node);
        else if (node.getWayRefs().isEmpty())
          toDelete.add(node.getId());
      } else {
        LOG.warning("No definition found for referenced node " + node.getId() + ".");
        toDelete.add(node.getId());
      }
    }
    for (long id : toDelete) {
      nodes.remove(id);
    }
    BoundingBox bbAllNodes = new BoundingBox();
    bbAllNodes.adjust(nodes.values());
    bbAllNodes.adjust(pois);
    if (boundingBox == null)
      boundingBox = bbAllNodes;
    else
      boundingBox.intersectWith(bbAllNodes);
    applyClassifierAndUpdateTree(bbAllNodes);
    fireMapDataEvent(new MapEvent(this, MapEvent.Type.MAP_NEW));
  }

  /**
   * Applies the current entity classifier to all currently maintained map
   * entities and creates a new entity tree with all relevant ways and points
   * of interest.
   */
  protected void applyClassifierAndUpdateTree(BoundingBox bb) {
    entityTree = new KDTree(bb, 8000, 60);
    for (MapWay way : ways.values())
      updateEntityViewInfo(way, true);
    for (MapNode poi : pois)
      updateEntityViewInfo(poi, true);
    for (MapNode mark : markers)
      updateEntityViewInfo(mark, false);
    for (Track track : tracks)
      updateEntityViewInfo(track, false);
  }

  /**
   * Updates the view information of a given entity by means of the current
   * entity classifier. If suitable viewing information was found and
   * <code>addToTree</code> is true, the entity is added to the entity tree.
   */
  private void updateEntityViewInfo(MapEntity entity, boolean addToTree) {
    EntityViewInfo info = null;
    if (entityClassifier != null)
      info = entityClassifier.classify(entity);
    entity.setViewInfo(info);
    if (addToTree && info != null)
      entityTree.insertEntity((DefaultMapEntity) entity);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public MapNode getNearestWayNode(Position pos, MapWayFilter filter) {
    MapNode node = pos.selectNearest(nodes.values(), filter);
    return node;
  }

  /** Reduces the level of detail by selecting some of the given nodes. */
  @Override
  public List<MapNode> getWayNodes(MapWay way, float scale) {
    List<MapNode> wayNodes = way.getNodes();
    int zoomLevel;
    if (scale <= 1f / 10000000)
      zoomLevel = 3;
    else if (scale <= 1f / 750000)
      zoomLevel = 2;
    else if (scale <= 1f / 350000)
      zoomLevel = 1;
    else
      zoomLevel = 0;
    if (zoomLevel > 0) {
      int size = wayNodes.size();
      List<MapNode> result = new ArrayList<MapNode>(size / zoomLevel + 2);
      int i = 0;
      for (MapNode node : wayNodes) {
        if (i == 0 || i == size - 1
            || node.getId() % (4 * zoomLevel) == 0)
          result.add(node);
        i++;
      }
      if (wayNodes.get(0) == wayNodes.get(size - 1) && result.size() < 4)
        result.clear();
      return result;
    } else {
      return Collections.unmodifiableList(wayNodes);
    }
  }

  /** {@inheritDoc} */
  @Override
  public int getPoiCount() {
    return pois.size();
  }

  /** {@inheritDoc} */
  @Override
  public List<MapNode> getPois(BoundingBox bb) {
    // not really efficient but ok for small maps...
    List<MapNode> result = new ArrayList<MapNode>();
    for (MapNode poi : pois)
      if (bb.isInside(poi.getLat(), poi.getLon()))
        result.add(poi);
    return result;
  }

  /** {@inheritDoc} */
  @Override
  public List<MapNode> getPlaces(String name) {
    String pattern = name.toLowerCase();
    List<MapNode> results = new ArrayList<MapNode>();
    for (MapNode node : pois) {
      if (node.getAttributeValue("place") != null
          && node.getName() != null
          && node.getName().toLowerCase().startsWith(pattern))
        results.add(node);
    }
    return results;
  }

  /** {@inheritDoc} */
  @Override
  public void clearMarkersAndTracks() {
    markers.clear();
    tracks.clear();
    fireMapDataEvent(new MapEvent(this, MapEvent.Type.MAP_MODIFIED));
  }

  /** {@inheritDoc} */
  @Override
  public List<MapEntity> getVisibleMarkersAndTracks(float scale) {
    List<MapEntity> result = new ArrayList<MapEntity>();
    for (MapNode marker : markers)
      if (marker.getViewInfo() != null
          && marker.getViewInfo().getMinVisibleScale() <= scale)
        result.add(marker);
    for (Track track : tracks)
      if (track.getViewInfo() != null
          && track.getViewInfo().getMinVisibleScale() <= scale)
        result.add(track);
    return result;
  }

  /** {@inheritDoc} */
  @Override
  public MapNode addMarker(float lat, float lon) {
    long id = 1;
    for (MapNode node : markers)
      if (node.getId() >= id)
        id = node.getId() + 1;
    MapNode node = new DefaultMapNode(id);
    node.setName(Long.toString(id));
    List<EntityAttribute> atts = new ArrayList<EntityAttribute>(1);
    atts.add(new EntityAttribute("marker", "yes"));
    node.setAttributes(atts);
    node.setPosition(lat, lon);
    updateEntityViewInfo(node, false);
    markers.add(node);
    fireMapDataEvent(new MapEvent(this, MapEvent.Type.MARKER_ADDED, node
        .getId()));
    return node;
  }

  /** {@inheritDoc} */
  @Override
  public void removeMarker(MapNode marker) {
    markers.remove(marker);
    fireMapDataEvent(new MapEvent(this, MapEvent.Type.MARKER_REMOVED,
        marker.getId()));
  }

  /** {@inheritDoc} */
  @Override
  public List<MapNode> getMarkers() {
    return markers;
  }

  /** {@inheritDoc} */
  @Override
  public void clearTrack(String trackName) {
    Track track = getTrack(trackName);
    if (track != null) {
      tracks.remove(track);
      fireMapDataEvent(new MapEvent(this, MapEvent.Type.MAP_MODIFIED));
    }
  }

  /** {@inheritDoc} */
  @Override
  public void createTrack(String trackName, List<Position> positions) {
    clearTrack(trackName);
    Track track = new DefaultTrack(nextTrackId++, trackName, trackName);
    updateEntityViewInfo(track, false);
    tracks.add(track);
    for (Position pos : positions)
      track.addNode(pos);
    fireMapDataEvent(new MapEvent(this, MapEvent.Type.TRACK_MODIFIED, track
        .getId()));
  }

  /** {@inheritDoc} */
  @Override
  public void addToTrack(String trackName, Position pos) {
    Track track = getTrack(trackName);
    if (track == null) {
      track = new DefaultTrack(nextTrackId++, trackName, trackName);
      updateEntityViewInfo(track, false);
      tracks.add(track);
    }
    track.addNode(pos);
    fireMapDataEvent(new MapEvent(this, MapEvent.Type.TRACK_MODIFIED, track
        .getId()));
  }

  /** {@inheritDoc} */
  @Override
  public List<Track> getTracks() {
    return tracks;
  }

  /** {@inheritDoc} */
  @Override
  public Track getTrack(long trackId) {
    for (Track track : tracks)
      if (track.getId() == trackId)
        return track;
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public Track getTrack(String trackName) {
    for (Track track : tracks)
      if (track.getName().equals(trackName))
        return track;
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public EntityFinder getEntityFinder() {
    EntityFinder result = new DefaultEntityFinder(this);
    return result;
  }

  /** {@inheritDoc} */
  @Override
  public Object[][] getStatistics() {
    Object[][] result = new Object[3][2];
    result[0][0] = "Nodes";
    result[0][1] = nodes.size();
    result[1][0] = "Ways";
    result[1][1] = ways.size();
    result[2][0] = "POIs";
    result[2][1] = pois.size();
    return result;
  }

  /** {@inheritDoc} */
  @Override
  public void visitEntities(EntityVisitor visitor, BoundingBox vbox,
      float scale) {
    if (entityTree != null)
      entityTree.visitEntities(visitor, vbox, scale);
  }

  /** Returns a kd-tree with all entities. */
  public KDTree getEntityTree() {
    return entityTree;
  }

  /** {@inheritDoc} */
  @Override
  public void addMapDataEventListener(MapEventListener listener) {
    listeners.add(listener);
  }

  /** {@inheritDoc} */
  @Override
  public void removeMapDataEventListener(MapEventListener listener) {
    listeners.remove(listener);
  }

  /** {@inheritDoc} */
  @Override
  public void fireMapDataEvent(MapEvent event) {
    for (MapEventListener listener : listeners)
      listener.eventHappened(event);
  }
}
TOP

Related Classes of aimax.osm.data.impl.DefaultMap

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.