Index: src/org/openstreetmap/josm/actions/ExtensionFileFilter.java
===================================================================
--- src/org/openstreetmap/josm/actions/ExtensionFileFilter.java	(revision 4459)
+++ src/org/openstreetmap/josm/actions/ExtensionFileFilter.java	(working copy)
@@ -40,6 +40,7 @@
         String[] importerNames = {
                 "org.openstreetmap.josm.io.OsmImporter",
                 "org.openstreetmap.josm.io.OsmGzipImporter",
+                "org.openstreetmap.josm.io.PbfImporter",
                 "org.openstreetmap.josm.io.GpxImporter",
                 "org.openstreetmap.josm.io.NMEAImporter",
                 "org.openstreetmap.josm.io.OsmBzip2Importer",
Index: src/org/openstreetmap/josm/data/coor/LatLon.java
===================================================================
--- src/org/openstreetmap/josm/data/coor/LatLon.java	(revision 4459)
+++ src/org/openstreetmap/josm/data/coor/LatLon.java	(working copy)
@@ -63,6 +63,15 @@
     public static boolean isValidLon(double lon) {
         return lon >= -180d && lon <= 180d;
     }
+    
+    /**
+     * Replies true if lat is in the range [-90,90] and lon is in the range [-180,180]
+     *
+     * @return true if lat is in the range [-90,90] and lon is in the range [-180,180]
+     */
+    public boolean isValid() {
+        return isValidLat(lat()) && isValidLon(lon());
+    }
 
     /**
      * Replies the coordinate in degrees/minutes/seconds format
Index: src/org/openstreetmap/josm/io/AbstractReader.java
===================================================================
--- src/org/openstreetmap/josm/io/AbstractReader.java	(revision 0)
+++ src/org/openstreetmap/josm/io/AbstractReader.java	(revision 0)
@@ -0,0 +1,213 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.PrimitiveId;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
+import org.openstreetmap.josm.data.osm.Way;
+
+/**
+ * Base class for OsmReader (XML file format) and PbfReader (Protobuf file format)
+ * @author Vincent
+ *
+ */
+public abstract class AbstractReader {
+    
+    /**
+     * Used as a temporary storage for relation members, before they
+     * are resolved into pointers to real objects.
+     */
+    protected static class RelationMemberData {
+        public OsmPrimitiveType type;
+        public long id;
+        public String role;
+    }
+
+    /**
+     * The dataset to add parsed objects to.
+     */
+    protected final DataSet ds = new DataSet();
+
+    protected Changeset uploadChangeset;
+
+    /** the map from external ids to read OsmPrimitives. External ids are
+     * longs too, but in contrast to internal ids negative values are used
+     * to identify primitives unknown to the OSM server
+     */
+    protected Map<PrimitiveId, OsmPrimitive> externalIdMap = new HashMap<PrimitiveId, OsmPrimitive>();
+
+    /**
+     * Data structure for the remaining way objects
+     */
+    protected Map<Long, Collection<Long>> ways = new HashMap<Long, Collection<Long>>();
+
+    /**
+     * Data structure for relation objects
+     */
+    protected Map<Long, Collection<RelationMemberData>> relations = new HashMap<Long, Collection<RelationMemberData>>();
+
+    /**
+     * Replies the parsed data set
+     *
+     * @return the parsed data set
+     */
+    public DataSet getDataSet() {
+        return ds;
+    }
+    
+    /**
+     * Processes the parsed nodes after parsing. Just adds them to
+     * the dataset
+     *
+     */
+    private void processNodesAfterParsing() {
+        for (OsmPrimitive primitive: externalIdMap.values()) {
+            if (primitive instanceof Node) {
+                this.ds.addPrimitive(primitive);
+            }
+        }
+    }
+
+    /**
+     * Processes the ways after parsing. Rebuilds the list of nodes of each way and
+     * adds the way to the dataset
+     *
+     * @throws IllegalDataException thrown if a data integrity problem is detected
+     */
+    private void processWaysAfterParsing() throws IllegalDataException{
+        for (Long externalWayId: ways.keySet()) {
+            Way w = (Way)externalIdMap.get(new SimplePrimitiveId(externalWayId, OsmPrimitiveType.WAY));
+            List<Node> wayNodes = new ArrayList<Node>();
+            for (long id : ways.get(externalWayId)) {
+                Node n = (Node)externalIdMap.get(new SimplePrimitiveId(id, OsmPrimitiveType.NODE));
+                if (n == null) {
+                    if (id <= 0)
+                        throw new IllegalDataException (
+                                tr("Way with external ID ''{0}'' includes missing node with external ID ''{1}''.",
+                                        externalWayId,
+                                        id));
+                    // create an incomplete node if necessary
+                    //
+                    n = (Node)ds.getPrimitiveById(id,OsmPrimitiveType.NODE);
+                    if (n == null) {
+                        n = new Node(id);
+                        ds.addPrimitive(n);
+                    }
+                }
+                if (n.isDeleted()) {
+                    System.out.println(tr("Deleted node {0} is part of way {1}", id, w.getId()));
+                } else {
+                    wayNodes.add(n);
+                }
+            }
+            w.setNodes(wayNodes);
+            if (w.hasIncompleteNodes()) {
+                  System.out.println(tr("Way {0} with {1} nodes has incomplete nodes because at least one node was missing in the loaded data.",
+                          externalWayId, w.getNodesCount()));
+            }
+            ds.addPrimitive(w);
+        }
+    }
+
+    /**
+     * Completes the parsed relations with its members.
+     *
+     * @throws IllegalDataException thrown if a data integrity problem is detected, i.e. if a
+     * relation member refers to a local primitive which wasn't available in the data
+     *
+     */
+    private void processRelationsAfterParsing() throws IllegalDataException {
+
+        // First add all relations to make sure that when relation reference other relation, the referenced will be already in dataset
+        for (Long externalRelationId : relations.keySet()) {
+            Relation relation = (Relation) externalIdMap.get(
+                    new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION)
+            );
+            ds.addPrimitive(relation);
+        }
+
+        for (Long externalRelationId : relations.keySet()) {
+            Relation relation = (Relation) externalIdMap.get(
+                    new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION)
+            );
+            List<RelationMember> relationMembers = new ArrayList<RelationMember>();
+            for (RelationMemberData rm : relations.get(externalRelationId)) {
+                OsmPrimitive primitive = null;
+
+                // lookup the member from the map of already created primitives
+                primitive = externalIdMap.get(new SimplePrimitiveId(rm.id, rm.type));
+
+                if (primitive == null) {
+                    if (rm.id <= 0)
+                        // relation member refers to a primitive with a negative id which was not
+                        // found in the data. This is always a data integrity problem and we abort
+                        // with an exception
+                        //
+                        throw new IllegalDataException(
+                                tr("Relation with external id ''{0}'' refers to a missing primitive with external id ''{1}''.",
+                                        externalRelationId,
+                                        rm.id));
+
+                    // member refers to OSM primitive which was not present in the parsed data
+                    // -> create a new incomplete primitive and add it to the dataset
+                    //
+                    primitive = ds.getPrimitiveById(rm.id, rm.type);
+                    if (primitive == null) {
+                        switch (rm.type) {
+                        case NODE:
+                            primitive = new Node(rm.id); break;
+                        case WAY:
+                            primitive = new Way(rm.id); break;
+                        case RELATION:
+                            primitive = new Relation(rm.id); break;
+                        default: throw new AssertionError(); // can't happen
+                        }
+
+                        ds.addPrimitive(primitive);
+                        externalIdMap.put(new SimplePrimitiveId(rm.id, rm.type), primitive);
+                    }
+                }
+                if (primitive.isDeleted()) {
+                    System.out.println(tr("Deleted member {0} is used by relation {1}", primitive.getId(), relation.getId()));
+                } else {
+                    relationMembers.add(new RelationMember(rm.role, primitive));
+                }
+            }
+            relation.setMembers(relationMembers);
+        }
+    }
+
+    private void processChangesetAfterParsing() {
+        if (uploadChangeset != null) {
+            for (Map.Entry<String, String> e : uploadChangeset.getKeys().entrySet()) {
+                ds.addChangeSetTag(e.getKey(), e.getValue());
+            }
+        }
+    }
+    
+    protected final void prepareDataSet() throws IllegalDataException {
+        try {
+            ds.beginUpdate();
+            processNodesAfterParsing();
+            processWaysAfterParsing();
+            processRelationsAfterParsing();
+            processChangesetAfterParsing();
+        } finally {
+            ds.endUpdate();
+        }
+    }
+}
Index: src/org/openstreetmap/josm/io/AllFormatsImporter.java
===================================================================
--- src/org/openstreetmap/josm/io/AllFormatsImporter.java	(revision 4459)
+++ src/org/openstreetmap/josm/io/AllFormatsImporter.java	(working copy)
@@ -12,7 +12,7 @@
  */
 public class AllFormatsImporter extends FileImporter {
     public AllFormatsImporter() {
-        super(new ExtensionFileFilter("osm,xml,osm.gz,osm.bz2,osm.bz,gpx,gpx.gz,nmea,nme,nma,log,txt,wms,jpg", "", tr("All Formats")
+        super(new ExtensionFileFilter("osm,xml,osm.gz,osm.bz2,osm.bz,osm.pbf,gpx,gpx.gz,nmea,nme,nma,log,txt,wms,jpg", "", tr("All Formats")
                     + " (*.gpx *.osm *.nmea *.jpg ...)"));
     }
     @Override public boolean acceptFile(File pathname) {
Index: src/org/openstreetmap/josm/io/OsmImporter.java
===================================================================
--- src/org/openstreetmap/josm/io/OsmImporter.java	(revision 4459)
+++ src/org/openstreetmap/josm/io/OsmImporter.java	(working copy)
@@ -42,6 +42,10 @@
     protected void importData(InputStream in, final File associatedFile) throws IllegalDataException {
         final DataSet dataSet = OsmReader.parseDataSet(in, NullProgressMonitor.INSTANCE);
         final OsmDataLayer layer = new OsmDataLayer(dataSet, associatedFile.getName(), associatedFile);
+        addDataLayer(dataSet, layer, associatedFile.getPath());
+    }
+    
+    protected void addDataLayer(final DataSet dataSet, final OsmDataLayer layer, final String filePath) {
         // FIXME: remove UI stuff from IO subsystem
         //
         Runnable uiStuff = new Runnable() {
@@ -50,7 +54,7 @@
                 if (dataSet.allPrimitives().isEmpty()) {
                     JOptionPane.showMessageDialog(
                             Main.parent,
-                            tr("No data found in file {0}.", associatedFile.getPath()),
+                            tr("No data found in file {0}.", filePath),
                             tr("Open OSM file"),
                             JOptionPane.INFORMATION_MESSAGE);
                 }
Index: src/org/openstreetmap/josm/io/OsmReader.java
===================================================================
--- src/org/openstreetmap/josm/io/OsmReader.java	(revision 4459)
+++ src/org/openstreetmap/josm/io/OsmReader.java	(working copy)
@@ -8,18 +8,14 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.xml.stream.Location;
 import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamReader;
 import javax.xml.stream.XMLStreamConstants;
 import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.Location;
+import javax.xml.stream.XMLStreamReader;
 
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.coor.LatLon;
@@ -28,14 +24,10 @@
 import org.openstreetmap.josm.data.osm.DataSource;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.NodeData;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
 import org.openstreetmap.josm.data.osm.PrimitiveData;
-import org.openstreetmap.josm.data.osm.PrimitiveId;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationData;
-import org.openstreetmap.josm.data.osm.RelationMember;
-import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
 import org.openstreetmap.josm.data.osm.Tagged;
 import org.openstreetmap.josm.data.osm.User;
 import org.openstreetmap.josm.data.osm.Way;
@@ -52,65 +44,14 @@
  * The XMLStreamReader cursor points to the start of the element, when the method is
  * entered, and it must point to the end of the same element, when it is exited.
  */
-public class OsmReader {
-
-    /**
-     * Used as a temporary storage for relation members, before they
-     * are resolved into pointers to real objects.
-     */
-    private static class RelationMemberData {
-        public OsmPrimitiveType type;
-        public long id;
-        public String role;
-    }
-
-    /**
-     * The dataset to add parsed objects to.
-     */
-    private DataSet ds = new DataSet();
+public class OsmReader extends AbstractReader {
 
     private XMLStreamReader parser;
 
-    /** the map from external ids to read OsmPrimitives. External ids are
-     * longs too, but in contrast to internal ids negative values are used
-     * to identify primitives unknown to the OSM server
-     */
-    private Map<PrimitiveId, OsmPrimitive> externalIdMap = new HashMap<PrimitiveId, OsmPrimitive>();
-
-    /**
-     * Data structure for the remaining way objects
-     */
-    private Map<Long, Collection<Long>> ways = new HashMap<Long, Collection<Long>>();
-
-    /**
-     * Data structure for relation objects
-     */
-    private Map<Long, Collection<RelationMemberData>> relations = new HashMap<Long, Collection<RelationMemberData>>();
-
-    private Changeset uploadChangeset;
-
-    /**
-     * constructor (for private use only)
-     *
-     * @see #parseDataSet(InputStream, DataSet, ProgressMonitor)
-     */
-    private OsmReader() {
-        externalIdMap = new HashMap<PrimitiveId, OsmPrimitive>();
-    }
-
     public void setParser(XMLStreamReader parser) {
         this.parser = parser;
     }
 
-    /**
-     * Replies the parsed data set
-     *
-     * @return the parsed data set
-     */
-    public DataSet getDataSet() {
-        return ds;
-    }
-
     protected void throwException(String msg) throws XMLStreamException {
         throw new OsmParsingException(msg, parser.getLocation());
     }
@@ -136,6 +77,7 @@
         parser.close();
     }
 
+    @SuppressWarnings("null")
     private void parseOsm() throws XMLStreamException {
         String v = parser.getAttributeValue(null, "version");
         if (v == null) {
@@ -351,6 +293,7 @@
         }
     }
 
+    @SuppressWarnings("null")
     private void parseTag(Tagged t) throws XMLStreamException {
         String key = parser.getAttributeValue(null, "k");
         String value = parser.getAttributeValue(null, "v");
@@ -579,136 +522,6 @@
     }
 
     /**
-     * Processes the parsed nodes after parsing. Just adds them to
-     * the dataset
-     *
-     */
-    protected void processNodesAfterParsing() {
-        for (OsmPrimitive primitive: externalIdMap.values()) {
-            if (primitive instanceof Node) {
-                this.ds.addPrimitive(primitive);
-            }
-        }
-    }
-
-    /**
-     * Processes the ways after parsing. Rebuilds the list of nodes of each way and
-     * adds the way to the dataset
-     *
-     * @throws IllegalDataException thrown if a data integrity problem is detected
-     */
-    protected void processWaysAfterParsing() throws IllegalDataException{
-        for (Long externalWayId: ways.keySet()) {
-            Way w = (Way)externalIdMap.get(new SimplePrimitiveId(externalWayId, OsmPrimitiveType.WAY));
-            List<Node> wayNodes = new ArrayList<Node>();
-            for (long id : ways.get(externalWayId)) {
-                Node n = (Node)externalIdMap.get(new SimplePrimitiveId(id, OsmPrimitiveType.NODE));
-                if (n == null) {
-                    if (id <= 0)
-                        throw new IllegalDataException (
-                                tr("Way with external ID ''{0}'' includes missing node with external ID ''{1}''.",
-                                        externalWayId,
-                                        id));
-                    // create an incomplete node if necessary
-                    //
-                    n = (Node)ds.getPrimitiveById(id,OsmPrimitiveType.NODE);
-                    if (n == null) {
-                        n = new Node(id);
-                        ds.addPrimitive(n);
-                    }
-                }
-                if (n.isDeleted()) {
-                    System.out.println(tr("Deleted node {0} is part of way {1}", id, w.getId()));
-                } else {
-                    wayNodes.add(n);
-                }
-            }
-            w.setNodes(wayNodes);
-            if (w.hasIncompleteNodes()) {
-                  System.out.println(tr("Way {0} with {1} nodes has incomplete nodes because at least one node was missing in the loaded data.",
-                          externalWayId, w.getNodesCount()));
-            }
-            ds.addPrimitive(w);
-        }
-    }
-
-    /**
-     * Completes the parsed relations with its members.
-     *
-     * @throws IllegalDataException thrown if a data integrity problem is detected, i.e. if a
-     * relation member refers to a local primitive which wasn't available in the data
-     *
-     */
-    private void processRelationsAfterParsing() throws IllegalDataException {
-
-        // First add all relations to make sure that when relation reference other relation, the referenced will be already in dataset
-        for (Long externalRelationId : relations.keySet()) {
-            Relation relation = (Relation) externalIdMap.get(
-                    new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION)
-            );
-            ds.addPrimitive(relation);
-        }
-
-        for (Long externalRelationId : relations.keySet()) {
-            Relation relation = (Relation) externalIdMap.get(
-                    new SimplePrimitiveId(externalRelationId, OsmPrimitiveType.RELATION)
-            );
-            List<RelationMember> relationMembers = new ArrayList<RelationMember>();
-            for (RelationMemberData rm : relations.get(externalRelationId)) {
-                OsmPrimitive primitive = null;
-
-                // lookup the member from the map of already created primitives
-                primitive = externalIdMap.get(new SimplePrimitiveId(rm.id, rm.type));
-
-                if (primitive == null) {
-                    if (rm.id <= 0)
-                        // relation member refers to a primitive with a negative id which was not
-                        // found in the data. This is always a data integrity problem and we abort
-                        // with an exception
-                        //
-                        throw new IllegalDataException(
-                                tr("Relation with external id ''{0}'' refers to a missing primitive with external id ''{1}''.",
-                                        externalRelationId,
-                                        rm.id));
-
-                    // member refers to OSM primitive which was not present in the parsed data
-                    // -> create a new incomplete primitive and add it to the dataset
-                    //
-                    primitive = ds.getPrimitiveById(rm.id, rm.type);
-                    if (primitive == null) {
-                        switch (rm.type) {
-                        case NODE:
-                            primitive = new Node(rm.id); break;
-                        case WAY:
-                            primitive = new Way(rm.id); break;
-                        case RELATION:
-                            primitive = new Relation(rm.id); break;
-                        default: throw new AssertionError(); // can't happen
-                        }
-
-                        ds.addPrimitive(primitive);
-                        externalIdMap.put(new SimplePrimitiveId(rm.id, rm.type), primitive);
-                    }
-                }
-                if (primitive.isDeleted()) {
-                    System.out.println(tr("Deleted member {0} is used by relation {1}", primitive.getId(), relation.getId()));
-                } else {
-                    relationMembers.add(new RelationMember(rm.role, primitive));
-                }
-            }
-            relation.setMembers(relationMembers);
-        }
-    }
-
-    private void processChangesetAfterParsing() {
-        if (uploadChangeset != null) {
-            for (Map.Entry<String, String> e : uploadChangeset.getKeys().entrySet()) {
-                ds.addChangeSetTag(e.getKey(), e.getValue());
-            }
-        }
-    }
-
-    /**
      * Parse the given input source and return the dataset.
      *
      * @param source the source input stream. Must not be null.
@@ -735,15 +548,7 @@
             progressMonitor.worked(1);
 
             progressMonitor.indeterminateSubTask(tr("Preparing data set..."));
-            reader.ds.beginUpdate();
-            try {
-                reader.processNodesAfterParsing();
-                reader.processWaysAfterParsing();
-                reader.processRelationsAfterParsing();
-                reader.processChangesetAfterParsing();
-            } finally {
-                reader.ds.endUpdate();
-            }
+            reader.prepareDataSet();
             progressMonitor.worked(1);
             return reader.getDataSet();
         } catch(IllegalDataException e) {
Index: src/org/openstreetmap/josm/io/PbfImporter.java
===================================================================
--- src/org/openstreetmap/josm/io/PbfImporter.java	(revision 0)
+++ src/org/openstreetmap/josm/io/PbfImporter.java	(revision 0)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.File;
+import java.io.InputStream;
+
+import org.openstreetmap.josm.actions.ExtensionFileFilter;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+
+/**
+ * @author Vincent
+ *
+ */
+public class PbfImporter extends OsmImporter {
+    
+    public PbfImporter() {
+        super(new ExtensionFileFilter("osm.pbf", "osm.pbf", tr("OSM Server Files PBF compressed") + " (*.osm.pbf)"));
+    }
+
+    /* (non-Javadoc)
+     * @see org.openstreetmap.josm.io.OsmImporter#importData(java.io.InputStream, java.io.File)
+     */
+    @Override
+    protected void importData(InputStream in, File associatedFile) throws IllegalDataException {
+        final DataSet dataSet = PbfReader.parseDataSet(in, NullProgressMonitor.INSTANCE);
+        final OsmDataLayer layer = new OsmDataLayer(dataSet, associatedFile.getName(), associatedFile);
+        addDataLayer(dataSet, layer, associatedFile.getPath());
+    }
+}
Index: src/org/openstreetmap/josm/io/PbfReader.java
===================================================================
--- src/org/openstreetmap/josm/io/PbfReader.java	(revision 0)
+++ src/org/openstreetmap/josm/io/PbfReader.java	(revision 0)
@@ -0,0 +1,275 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.User;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+
+import crosby.binary.BinaryParser;
+import crosby.binary.Osmformat;
+import crosby.binary.Osmformat.DenseNodes;
+import crosby.binary.Osmformat.HeaderBlock;
+import crosby.binary.file.BlockInputStream;
+import crosby.binary.file.FileBlockPosition;
+
+/**
+ * @author Vincent
+ *
+ */
+public class PbfReader extends AbstractReader {
+    
+    protected class PbfParser extends BinaryParser {
+
+        public IllegalDataException exception = null;
+        
+        @Override
+        protected void parse(HeaderBlock header) {
+        }
+
+        /* (non-Javadoc)
+         * @see crosby.binary.BinaryParser#skipBlock(crosby.binary.file.FileBlockPosition)
+         */
+        @Override
+        public boolean skipBlock(FileBlockPosition block) {
+            return exception != null;
+        }
+        
+        protected void checkCoordinates(LatLon coor) throws IllegalDataException {
+            if (!coor.isValid()) {
+                throw new IllegalDataException(tr("Invalid coordinates: {0}", coor));
+            }
+        }
+
+        protected void checkChangesetId(long id) throws IllegalDataException {
+            if (id > Integer.MAX_VALUE) {
+                throw new IllegalDataException(tr("Invalid changeset id: {0}", id));
+            }
+        }
+        
+        protected void checkTimestamp(long timestamp) throws IllegalDataException {
+            if (timestamp < 0) {
+                throw new IllegalDataException(tr("Invalid timestamp: {0}", timestamp));
+            }
+        }
+
+        @Override
+        protected void parseDense(DenseNodes nodes) {
+            if (exception == null) {
+                try {
+                    int keyIndex = 0;
+                    // Almost all data is DELTA coded
+                    long nodeId = 0;
+                    long nodeLat = 0;
+                    long nodeLon = 0;
+                    long changesetId = 0;
+                    int uid = 0;
+                    int suid = 0;
+                    long timestamp = 0;
+                    for (int i = 0; i < nodes.getIdCount(); i++) {
+                        // Id (delta) and version (normal)
+                        Node node = new Node(nodeId+=nodes.getId(i), nodes.getDenseinfo().getVersion(i));
+                        // Lat/Lon (delta)
+                        node.setCoor(new LatLon(parseLat(nodeLat+=nodes.getLat(i)), parseLon(nodeLon+=nodes.getLon(i))));
+                        checkCoordinates(node.getCoor());
+                        // Changeset (delta)
+                        checkChangesetId(changesetId+=nodes.getDenseinfo().getChangeset(i));
+                        node.setChangesetId((int) changesetId);
+                        // User (delta)
+                        node.setUser(User.createOsmUser(uid+=nodes.getDenseinfo().getUid(i), getStringById(suid+=nodes.getDenseinfo().getUserSid(i))));
+                        // Timestamp (delta)
+                        checkTimestamp(timestamp+=nodes.getDenseinfo().getTimestamp(i));
+                        node.setTimestamp(new Date(timestamp));
+                        // A single table contains all keys/values of all nodes.
+                        // Each node's tags are encoded in alternating <key_id> <value_id>.
+                        // A single stringid of 0 delimit when the tags of a node ends and the tags of the next node begin.
+                        Map<String, String> keys = new HashMap<String, String>();
+                        while (keyIndex < nodes.getKeysValsCount()) {
+                            int key_id = nodes.getKeysVals(keyIndex++);
+                            if (key_id == 0) {
+                                break; // End of current node's tags
+                            } else if (keyIndex < nodes.getKeysValsCount()) {
+                                int value_id = nodes.getKeysVals(keyIndex++);
+                                keys.put(getStringById(key_id), getStringById(value_id));
+                            } else {
+                                throw new IllegalDataException(tr("Invalid DenseNodes key/values table"));
+                            }
+                        }
+                        node.setKeys(keys);
+                        externalIdMap.put(node.getPrimitiveId(), node);
+                    }
+                } catch (IllegalDataException e) {
+                    exception = e;
+                }
+            }
+        }
+
+        @Override
+        protected void parseNodes(List<Osmformat.Node> osmNodes) {
+            if (exception == null) {
+                try {
+                    for (Osmformat.Node n : osmNodes) {
+                        Node node = new Node(n.getId(), n.getInfo().getVersion());
+                        node.setCoor(new LatLon(parseLat(n.getLat()), parseLon(n.getLon())));
+                        checkCoordinates(node.getCoor());
+                        checkChangesetId(n.getInfo().getChangeset());
+                        node.setChangesetId((int) n.getInfo().getChangeset());
+                        node.setUser(User.createOsmUser(n.getInfo().getUid(), getStringById(n.getInfo().getUserSid())));
+                        checkTimestamp(n.getInfo().getTimestamp());
+                        node.setTimestamp(new Date(n.getInfo().getTimestamp()));
+                        Map<String, String> keys = new HashMap<String, String>();
+                        for (int i=0; i<n.getKeysCount(); i++) {
+                            keys.put(getStringById(n.getKeys(i)), getStringById(n.getVals(i)));
+                        }
+                        node.setKeys(keys);
+                        externalIdMap.put(node.getPrimitiveId(), node);
+                    }
+                } catch (IllegalDataException e) {
+                    exception = e;
+                }
+            }
+        }
+        
+        @Override
+        protected void parseWays(List<Osmformat.Way> osmWays) {
+            if (exception == null) {
+                try {
+                    for (Osmformat.Way w : osmWays) {
+                        Way way = new Way(w.getId(), w.getInfo().getVersion());
+                        checkChangesetId(w.getInfo().getChangeset());
+                        way.setChangesetId((int) w.getInfo().getChangeset());
+                        way.setUser(User.createOsmUser(w.getInfo().getUid(), getStringById(w.getInfo().getUserSid())));
+                        checkTimestamp(w.getInfo().getTimestamp());
+                        way.setTimestamp(new Date(w.getInfo().getTimestamp()));
+                        Map<String, String> keys = new HashMap<String, String>();
+                        for (int i=0; i<w.getKeysCount(); i++) {
+                            keys.put(getStringById(w.getKeys(i)), getStringById(w.getVals(i)));
+                        }
+                        way.setKeys(keys);
+                        long previousId = 0; // Node ids are delta coded
+                        Collection<Long> nodeIds = new ArrayList<Long>();
+                        for (Long id : w.getRefsList()) {
+                            nodeIds.add(previousId+=id);
+                        }
+                        ways.put(way.getUniqueId(), nodeIds);
+                        externalIdMap.put(way.getPrimitiveId(), way);
+                    }
+                } catch (IllegalDataException e) {
+                    exception = e;
+                }
+            }
+        }
+        
+        @Override
+        protected void parseRelations(List<Osmformat.Relation> osmRels) {
+            if (exception == null) {
+                try {
+                    for (Osmformat.Relation r : osmRels) {
+                        Relation rel = new Relation(r.getId(), r.getInfo().getVersion());
+                        checkChangesetId(r.getInfo().getChangeset());
+                        rel.setChangesetId((int) r.getInfo().getChangeset());
+                        rel.setUser(User.createOsmUser(r.getInfo().getUid(), getStringById(r.getInfo().getUserSid())));
+                        checkTimestamp(r.getInfo().getTimestamp());
+                        rel.setTimestamp(new Date(r.getInfo().getTimestamp()));
+                        Map<String, String> keys = new HashMap<String, String>();
+                        for (int i=0; i<r.getKeysCount(); i++) {
+                            keys.put(getStringById(r.getKeys(i)), getStringById(r.getVals(i)));
+                        }
+                        rel.setKeys(keys);
+                        long previousId = 0; // Member ids are delta coded
+                        Collection<RelationMemberData> members = new ArrayList<RelationMemberData>();
+                        for (int i = 0; i<r.getMemidsCount(); i++) {
+                            RelationMemberData rmd = new RelationMemberData();
+                            rmd.id = previousId+=r.getMemids(i);
+                            rmd.role = getStringById(r.getRolesSid(i));
+                            switch (r.getTypes(i)) {
+                                case NODE:
+                                    rmd.type = OsmPrimitiveType.NODE;
+                                    break;
+                                case WAY:
+                                    rmd.type = OsmPrimitiveType.WAY;
+                                    break;
+                                case RELATION:
+                                    rmd.type = OsmPrimitiveType.RELATION;
+                                    break;
+                            }
+                            members.add(rmd);
+                        }
+                        relations.put(rel.getUniqueId(), members);
+                        externalIdMap.put(rel.getPrimitiveId(), rel);
+                    }
+                } catch (IllegalDataException e) {
+                    exception = e;
+                }
+            }
+        }
+
+        @Override
+        public void complete() {
+        }
+    }
+
+    private PbfParser parser = new PbfParser();
+    
+    /**
+     * Parse the given input source and return the dataset.
+     *
+     * @param source the source input stream. Must not be null.
+     * @param progressMonitor  the progress monitor. If null, {@see NullProgressMonitor#INSTANCE} is assumed
+     *
+     * @return the dataset with the parsed data
+     * @throws IllegalDataException thrown if the an error was found while parsing the data from the source
+     * @throws IllegalArgumentException thrown if source is null
+     */
+    public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
+        if (progressMonitor == null) {
+            progressMonitor = NullProgressMonitor.INSTANCE;
+        }
+        CheckParameterUtil.ensureParameterNotNull(source, "source");
+
+        PbfReader reader = new PbfReader();
+        
+        try {
+            progressMonitor.beginTask(tr("Prepare OSM data...", 2));
+            progressMonitor.indeterminateSubTask(tr("Reading OSM data..."));
+
+            reader.parse(source);
+            progressMonitor.worked(1);
+
+            progressMonitor.indeterminateSubTask(tr("Preparing data set..."));
+            reader.prepareDataSet();
+            progressMonitor.worked(1);
+            return reader.getDataSet();
+        } catch (IllegalDataException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new IllegalDataException(e);
+        } finally {
+            progressMonitor.finishTask();
+        }
+    }
+
+    public void parse(InputStream source) throws IOException, IllegalDataException {
+        new BlockInputStream(source, parser).process();
+        if (parser.exception != null) {
+            throw parser.exception;
+        }
+    }
+}
