diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbbe474b8760890f3f2b37efae481686c0fc9a76..a3330712bf05ae708b97e14b5c292d31d07a2462 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - MIT license file.
 
 ### Changed
+- **Set the minimum Tiled TMX version to 0.13.**
+- `Tileset::tilecount` is no longer optional.
 - `Layer` has been renamed to `TileLayer`, and the original `Layer` structure is now used
   for common data from all layer types.
 - `Map` now has a single `layers` member which contains layers of all types in order.
@@ -29,7 +31,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - `parse_with_path` -> `Map::parse_reader`.
 - `parse_tileset` -> `Tileset::parse`.
 - All mentions of `Colour` have been changed to `Color` for consistency with the Tiled dataformat.
-- `Map::get_tileset_by_gid` -> `Map::tileset_by_gid`.
 - `Layer::tiles` changed from `Vec<Vec<LayerTile>>` to `Vec<LayerTile>`.
 - Tile now has `image` instead of `images`. ([Issue comment](https://github.com/mapeditor/rs-tiled/issues/103#issuecomment-940773123))
 - Tileset now has `image` instead of `images`.
diff --git a/README.md b/README.md
index fd40af5dd994e39fd60c3ebfe3aeddbf4cffe6c5..33eae93c5ffbe0fa7d458733bbb624f2c79d86d5 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,8 @@ tiled = "0.9.5"
 
 to the dependencies section of your Cargo.toml.
 
+The minimum supported TMX version is 0.13.
+
 ### Example
 
 ```rust
diff --git a/examples/main.rs b/examples/main.rs
index d39dd5239372454e96562194133e769bf8e322bf..4269d8d55002716bc4a35a997493091c6f25ea0d 100644
--- a/examples/main.rs
+++ b/examples/main.rs
@@ -1,7 +1,50 @@
-use tiled::Map;
+use std::path::PathBuf;
+
+use tiled::{FilesystemResourceCache, Map};
 
 fn main() {
-    let map = Map::parse_file("assets/tiled_base64_zlib.tmx").unwrap();
-    println!("{:?}", map);
-    println!("{:?}", map.tileset_by_gid(22));
+    // Create a new resource cache. This is a structure that holds references to loaded
+    // assets such as tilesets so that they only get loaded once.
+    // [`FilesystemResourceCache`] is a implementation of [`tiled::ResourceCache`] that
+    // identifies resources by their path in the filesystem.
+    let mut cache = FilesystemResourceCache::new();
+
+    let map_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
+        .join("assets/tiled_base64_zlib.tmx");
+    let map = Map::parse_file(map_path, &mut cache).unwrap();
+
+    for layer in map.layers() {
+        print!("Layer \"{}\":\n\t", layer.data().name);
+
+        match layer.layer_type() {
+            tiled::LayerType::TileLayer(layer) => match layer.data() {
+                tiled::TileLayerData::Finite(data) => println!(
+                    "Finite tile layer with width = {} and height = {}; ID of tile @ (0,0): {}",
+                    data.width(),
+                    data.height(),
+                    layer.get_tile(0, 0).unwrap().id
+                ),
+                tiled::TileLayerData::Infinite(data) => {
+                    // This is prone to change! Infinite layers will be refactored before 0.10.0
+                    // releases.
+                    println!("Infinite tile layer with {} chunks", data.chunks.len())
+                }
+            },
+
+            tiled::LayerType::ObjectLayer(layer) => {
+                println!("Object layer with {} objects", layer.data().objects.len())
+            }
+
+            tiled::LayerType::ImageLayer(layer) => {
+                println!(
+                    "Image layer with {}",
+                    match &layer.data().image {
+                        Some(img) =>
+                            format!("an image with source = {}", img.source.to_string_lossy()),
+                        None => "no image".to_owned(),
+                    }
+                )
+            }
+        }
+    }
 }
diff --git a/src/cache.rs b/src/cache.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0a171b6db77748e10f461946ef3b8eeafe463b1b
--- /dev/null
+++ b/src/cache.rs
@@ -0,0 +1,55 @@
+use std::{
+    collections::HashMap,
+    path::{Path, PathBuf},
+    rc::Rc,
+};
+
+use crate::Tileset;
+
+pub type ResourcePath = Path;
+pub type ResourcePathBuf = PathBuf;
+
+pub trait ResourceCache {
+    fn get_tileset(&self, path: impl AsRef<ResourcePath>) -> Option<Rc<Tileset>>;
+    fn get_or_try_insert_tileset_with<F, E>(
+        &mut self,
+        path: ResourcePathBuf,
+        f: F,
+    ) -> Result<Rc<Tileset>, E>
+    where
+        F: FnOnce() -> Result<Tileset, E>;
+}
+
+/// A cache that identifies resources by their path in the user's filesystem.
+pub struct FilesystemResourceCache {
+    tilesets: HashMap<ResourcePathBuf, Rc<Tileset>>,
+}
+
+impl FilesystemResourceCache {
+    pub fn new() -> Self {
+        Self {
+            tilesets: HashMap::new(),
+        }
+    }
+}
+
+impl ResourceCache for FilesystemResourceCache {
+    fn get_tileset(&self, path: impl AsRef<ResourcePath>) -> Option<Rc<Tileset>> {
+        self.tilesets.get(path.as_ref()).map(Clone::clone)
+    }
+
+    fn get_or_try_insert_tileset_with<F, E>(
+        &mut self,
+        path: ResourcePathBuf,
+        f: F,
+    ) -> Result<Rc<Tileset>, E>
+    where
+        F: FnOnce() -> Result<Tileset, E>,
+    {
+        Ok(match self.tilesets.entry(path) {
+            std::collections::hash_map::Entry::Occupied(o) => o.into_mut(),
+            std::collections::hash_map::Entry::Vacant(v) => v.insert(Rc::new(f()?)),
+        }
+        .clone())
+    }
+}
diff --git a/src/error.rs b/src/error.rs
index 55c4d032fe695152bc40b9017fdb9a8db4e4b1d4..3b0f789a11c19476f2a103dfc335128dbf5a4669 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,4 +1,4 @@
-use std::fmt;
+use std::{fmt, path::PathBuf};
 
 #[derive(Debug, Copy, Clone)]
 pub enum ParseTileError {
@@ -23,23 +23,46 @@ pub enum TiledError {
     SourceRequired {
         object_to_parse: String,
     },
+    /// The path given is invalid because it isn't contained in any folder.
+    PathIsNotFile,
+    CouldNotOpenFile {
+        path: PathBuf,
+        err: std::io::Error,
+    },
+    /// There was an invalid tile in the map parsed.
+    InvalidTileFound,
     Other(String),
 }
 
 impl fmt::Display for TiledError {
     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
-        match *self {
-            TiledError::MalformedAttributes(ref s) => write!(fmt, "{}", s),
-            TiledError::DecompressingError(ref e) => write!(fmt, "{}", e),
-            TiledError::Base64DecodingError(ref e) => write!(fmt, "{}", e),
-            TiledError::XmlDecodingError(ref e) => write!(fmt, "{}", e),
-            TiledError::PrematureEnd(ref e) => write!(fmt, "{}", e),
+        match self {
+            TiledError::MalformedAttributes(s) => write!(fmt, "{}", s),
+            TiledError::DecompressingError(e) => write!(fmt, "{}", e),
+            TiledError::Base64DecodingError(e) => write!(fmt, "{}", e),
+            TiledError::XmlDecodingError(e) => write!(fmt, "{}", e),
+            TiledError::PrematureEnd(e) => write!(fmt, "{}", e),
             TiledError::SourceRequired {
                 ref object_to_parse,
             } => {
                 write!(fmt, "Tried to parse external {} without a file location, e.g. by using Map::parse_reader.", object_to_parse)
             }
-            TiledError::Other(ref s) => write!(fmt, "{}", s),
+            TiledError::PathIsNotFile => {
+                write!(
+                    fmt,
+                    "The path given is invalid because it isn't contained in any folder."
+                )
+            }
+            TiledError::CouldNotOpenFile { path, err } => {
+                write!(
+                    fmt,
+                    "Could not open '{}'. Error: {}",
+                    path.to_string_lossy(),
+                    err
+                )
+            }
+            TiledError::InvalidTileFound => write!(fmt, "Invalid tile found in map being parsed"),
+            TiledError::Other(s) => write!(fmt, "{}", s),
         }
     }
 }
@@ -47,14 +70,12 @@ impl fmt::Display for TiledError {
 // This is a skeleton implementation, which should probably be extended in the future.
 impl std::error::Error for TiledError {
     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
-        match *self {
-            TiledError::MalformedAttributes(_) => None,
-            TiledError::DecompressingError(ref e) => Some(e as &dyn std::error::Error),
-            TiledError::Base64DecodingError(ref e) => Some(e as &dyn std::error::Error),
-            TiledError::XmlDecodingError(ref e) => Some(e as &dyn std::error::Error),
-            TiledError::PrematureEnd(_) => None,
-            TiledError::SourceRequired { .. } => None,
-            TiledError::Other(_) => None,
+        match self {
+            TiledError::DecompressingError(e) => Some(e as &dyn std::error::Error),
+            TiledError::Base64DecodingError(e) => Some(e as &dyn std::error::Error),
+            TiledError::XmlDecodingError(e) => Some(e as &dyn std::error::Error),
+            TiledError::CouldNotOpenFile { err, .. } => Some(err as &dyn std::error::Error),
+            _ => None,
         }
     }
 }
diff --git a/src/image.rs b/src/image.rs
index a7cf8eff2874bc13208101e6e996c93ab313e614..7399f380e8d2dfdb1921a2b9ec2d8d9e4ba73737 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -1,9 +1,6 @@
-use std::{
-    io::Read,
-    path::{Path, PathBuf},
-};
+use std::path::{Path, PathBuf};
 
-use xml::{attribute::OwnedAttribute, EventReader};
+use xml::attribute::OwnedAttribute;
 
 use crate::{error::TiledError, properties::Color, util::*};
 
@@ -24,8 +21,8 @@ pub struct Image {
 }
 
 impl Image {
-    pub(crate) fn new<R: Read>(
-        parser: &mut EventReader<R>,
+    pub(crate) fn new(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
         attrs: Vec<OwnedAttribute>,
         path_relative_to: impl AsRef<Path>,
     ) -> Result<Image, TiledError> {
diff --git a/src/layers.rs b/src/layers.rs
deleted file mode 100644
index e75f345759ddf3f182f9b8195167c3a4f3b11ba9..0000000000000000000000000000000000000000
--- a/src/layers.rs
+++ /dev/null
@@ -1,303 +0,0 @@
-use std::{collections::HashMap, io::Read, path::Path};
-
-use xml::{attribute::OwnedAttribute, EventReader};
-
-use crate::{
-    error::TiledError,
-    image::Image,
-    objects::Object,
-    properties::{parse_properties, Color, Properties},
-    util::*,
-};
-
-/// Stores the proper tile gid, along with how it is flipped.
-// Maybe PartialEq and Eq should be custom, so that it ignores tile-flipping?
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub struct LayerTile {
-    pub gid: u32,
-    pub flip_h: bool,
-    pub flip_v: bool,
-    pub flip_d: bool,
-}
-
-const FLIPPED_HORIZONTALLY_FLAG: u32 = 0x80000000;
-const FLIPPED_VERTICALLY_FLAG: u32 = 0x40000000;
-const FLIPPED_DIAGONALLY_FLAG: u32 = 0x20000000;
-const ALL_FLIP_FLAGS: u32 =
-    FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG;
-
-impl LayerTile {
-    pub fn new(id: u32) -> LayerTile {
-        let flags = id & ALL_FLIP_FLAGS;
-        let gid = id & !ALL_FLIP_FLAGS;
-        let flip_d = flags & FLIPPED_DIAGONALLY_FLAG == FLIPPED_DIAGONALLY_FLAG; // Swap x and y axis (anti-diagonally) [flips over y = -x line]
-        let flip_h = flags & FLIPPED_HORIZONTALLY_FLAG == FLIPPED_HORIZONTALLY_FLAG; // Flip tile over y axis
-        let flip_v = flags & FLIPPED_VERTICALLY_FLAG == FLIPPED_VERTICALLY_FLAG; // Flip tile over x axis
-
-        LayerTile {
-            gid,
-            flip_h,
-            flip_v,
-            flip_d,
-        }
-    }
-}
-
-#[derive(Clone, PartialEq, Debug)]
-pub enum LayerType {
-    TileLayer(TileLayer),
-    ObjectLayer(ObjectLayer),
-    ImageLayer(ImageLayer),
-    // TODO: Support group layers
-}
-
-#[derive(Clone, Copy)]
-pub(crate) enum LayerTag {
-    TileLayer,
-    ObjectLayer,
-    ImageLayer,
-}
-
-#[derive(Clone, PartialEq, Debug)]
-pub struct Layer {
-    pub name: String,
-    pub id: u32,
-    pub visible: bool,
-    pub offset_x: f32,
-    pub offset_y: f32,
-    pub parallax_x: f32,
-    pub parallax_y: f32,
-    pub opacity: f32,
-    pub tint_color: Option<Color>,
-    pub properties: Properties,
-    pub layer_type: LayerType,
-}
-
-impl Layer {
-    pub(crate) fn new<R: Read>(
-        parser: &mut EventReader<R>,
-        attrs: Vec<OwnedAttribute>,
-        tag: LayerTag,
-        infinite: bool,
-        path_relative_to: Option<&Path>,
-    ) -> Result<Self, TiledError> {
-        let (
-            (opacity, tint_color, visible, offset_x, offset_y, parallax_x, parallax_y, name, id),
-            (),
-        ) = get_attrs!(
-            attrs,
-            optionals: [
-                ("opacity", opacity, |v:String| v.parse().ok()),
-                ("tintcolor", tint_color, |v:String| v.parse().ok()),
-                ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)),
-                ("offsetx", offset_x, |v:String| v.parse().ok()),
-                ("offsety", offset_y, |v:String| v.parse().ok()),
-                ("parallaxx", parallax_x, |v:String| v.parse().ok()),
-                ("parallaxy", parallax_y, |v:String| v.parse().ok()),
-                ("name", name, |v| Some(v)),
-                ("id", id, |v:String| v.parse().ok()),
-            ],
-            required: [
-            ],
-
-            TiledError::MalformedAttributes("layer parsing error, no id attribute found".to_string())
-        );
-
-        let (ty, properties) = match tag {
-            LayerTag::TileLayer => {
-                let (ty, properties) = TileLayer::new(parser, attrs, infinite)?;
-                (LayerType::TileLayer(ty), properties)
-            }
-            LayerTag::ObjectLayer => {
-                let (ty, properties) = ObjectLayer::new(parser, attrs)?;
-                (LayerType::ObjectLayer(ty), properties)
-            }
-            LayerTag::ImageLayer => {
-                let (ty, properties) = ImageLayer::new(parser, path_relative_to)?;
-                (LayerType::ImageLayer(ty), properties)
-            }
-        };
-
-        Ok(Self {
-            visible: visible.unwrap_or(true),
-            offset_x: offset_x.unwrap_or(0.0),
-            offset_y: offset_y.unwrap_or(0.0),
-            parallax_x: parallax_x.unwrap_or(1.0),
-            parallax_y: parallax_y.unwrap_or(1.0),
-            opacity: opacity.unwrap_or(1.0),
-            tint_color,
-            name: name.unwrap_or_default(),
-            id: id.unwrap_or(0),
-            properties,
-            layer_type: ty,
-        })
-    }
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct TileLayer {
-    pub width: u32,
-    pub height: u32,
-    /// The tiles are arranged in rows. Each tile is a number which can be used
-    ///  to find which tileset it belongs to and can then be rendered.
-    pub tiles: LayerData,
-}
-
-impl TileLayer {
-    pub(crate) fn new<R: Read>(
-        parser: &mut EventReader<R>,
-        attrs: Vec<OwnedAttribute>,
-        infinite: bool,
-    ) -> Result<(TileLayer, Properties), TiledError> {
-        let ((), (w, h)) = get_attrs!(
-            attrs,
-            optionals: [
-            ],
-            required: [
-                ("width", width, |v: String| v.parse().ok()),
-                ("height", height, |v: String| v.parse().ok()),
-            ],
-            TiledError::MalformedAttributes("layer parsing error, width and height attributes required".to_string())
-        );
-        let mut tiles: LayerData = LayerData::Finite(Default::default());
-        let mut properties = HashMap::new();
-        parse_tag!(parser, "layer", {
-            "data" => |attrs| {
-                if infinite {
-                    tiles = parse_infinite_data(parser, attrs)?;
-                } else {
-                    tiles = parse_data(parser, attrs)?;
-                }
-                Ok(())
-            },
-            "properties" => |_| {
-                properties = parse_properties(parser)?;
-                Ok(())
-            },
-        });
-
-        Ok((
-            TileLayer {
-                width: w,
-                height: h,
-                tiles: tiles,
-            },
-            properties,
-        ))
-    }
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub enum LayerData {
-    Finite(Vec<LayerTile>),
-    Infinite(HashMap<(i32, i32), Chunk>),
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct ImageLayer {
-    pub image: Option<Image>,
-}
-
-impl ImageLayer {
-    pub(crate) fn new<R: Read>(
-        parser: &mut EventReader<R>,
-        path_relative_to: Option<&Path>,
-    ) -> Result<(ImageLayer, Properties), TiledError> {
-        let mut image: Option<Image> = None;
-        let mut properties = HashMap::new();
-
-        parse_tag!(parser, "imagelayer", {
-            "image" => |attrs| {
-                image = Some(Image::new(parser, attrs, path_relative_to.ok_or(TiledError::SourceRequired{object_to_parse: "Image".to_string()})?)?);
-                Ok(())
-            },
-            "properties" => |_| {
-                properties = parse_properties(parser)?;
-                Ok(())
-            },
-        });
-        Ok((ImageLayer { image }, properties))
-    }
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct ObjectLayer {
-    pub objects: Vec<Object>,
-    pub colour: Option<Color>,
-}
-
-impl ObjectLayer {
-    pub(crate) fn new<R: Read>(
-        parser: &mut EventReader<R>,
-        attrs: Vec<OwnedAttribute>,
-    ) -> Result<(ObjectLayer, Properties), TiledError> {
-        let (c, ()) = get_attrs!(
-            attrs,
-            optionals: [
-                ("color", colour, |v:String| v.parse().ok()),
-            ],
-            required: [],
-            // this error should never happen since there are no required attrs
-            TiledError::MalformedAttributes("object group parsing error".to_string())
-        );
-        let mut objects = Vec::new();
-        let mut properties = HashMap::new();
-        parse_tag!(parser, "objectgroup", {
-            "object" => |attrs| {
-                objects.push(Object::new(parser, attrs)?);
-                Ok(())
-            },
-            "properties" => |_| {
-                properties = parse_properties(parser)?;
-                Ok(())
-            },
-        });
-        Ok((
-            ObjectLayer {
-                objects: objects,
-                colour: c,
-            },
-            properties,
-        ))
-    }
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct Chunk {
-    pub x: i32,
-    pub y: i32,
-    pub width: u32,
-    pub height: u32,
-    pub tiles: Vec<LayerTile>,
-}
-
-impl Chunk {
-    pub(crate) fn new<R: Read>(
-        parser: &mut EventReader<R>,
-        attrs: Vec<OwnedAttribute>,
-        encoding: Option<String>,
-        compression: Option<String>,
-    ) -> Result<Chunk, TiledError> {
-        let ((), (x, y, width, height)) = get_attrs!(
-            attrs,
-            optionals: [],
-            required: [
-                ("x", x, |v: String| v.parse().ok()),
-                ("y", y, |v: String| v.parse().ok()),
-                ("width", width, |v: String| v.parse().ok()),
-                ("height", height, |v: String| v.parse().ok()),
-            ],
-            TiledError::MalformedAttributes("layer must have a name".to_string())
-        );
-
-        let tiles = parse_data_line(encoding, compression, parser)?;
-
-        Ok(Chunk {
-            x,
-            y,
-            width,
-            height,
-            tiles,
-        })
-    }
-}
diff --git a/src/layers/image.rs b/src/layers/image.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0ea05c0a5ede06b3a62213e7e334b9fe364bc987
--- /dev/null
+++ b/src/layers/image.rs
@@ -0,0 +1,38 @@
+use std::{collections::HashMap, path::Path};
+
+use crate::{
+    parse_properties,
+    util::{parse_tag, XmlEventResult},
+    Image, MapWrapper, Properties, TiledError,
+};
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct ImageLayerData {
+    pub image: Option<Image>,
+}
+
+impl ImageLayerData {
+    pub(crate) fn new(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
+        map_path: &Path,
+    ) -> Result<(Self, Properties), TiledError> {
+        let mut image: Option<Image> = None;
+        let mut properties = HashMap::new();
+
+        let path_relative_to = map_path.parent().ok_or(TiledError::PathIsNotFile)?;
+
+        parse_tag!(parser, "imagelayer", {
+            "image" => |attrs| {
+                image = Some(Image::new(parser, attrs, path_relative_to)?);
+                Ok(())
+            },
+            "properties" => |_| {
+                properties = parse_properties(parser)?;
+                Ok(())
+            },
+        });
+        Ok((ImageLayerData { image }, properties))
+    }
+}
+
+pub type ImageLayer<'map> = MapWrapper<'map, ImageLayerData>;
diff --git a/src/layers/mod.rs b/src/layers/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b05004a1e199f5afea1d5c032e0fc0bc5d187803
--- /dev/null
+++ b/src/layers/mod.rs
@@ -0,0 +1,130 @@
+use std::path::Path;
+
+use xml::attribute::OwnedAttribute;
+
+use crate::{error::TiledError, properties::Properties, util::*, Map, MapTilesetGid, MapWrapper, Color};
+
+mod image;
+pub use image::*;
+mod object;
+pub use object::*;
+mod tile;
+pub use tile::*;
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum LayerDataType {
+    TileLayer(TileLayerData),
+    ObjectLayer(ObjectLayerData),
+    ImageLayer(ImageLayerData),
+    // TODO: Support group layers
+}
+
+#[derive(Clone, Copy)]
+pub(crate) enum LayerTag {
+    TileLayer,
+    ObjectLayer,
+    ImageLayer,
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct LayerData {
+    pub name: String,
+    pub id: u32,
+    pub visible: bool,
+    pub offset_x: f32,
+    pub offset_y: f32,
+    pub parallax_x: f32,
+    pub parallax_y: f32,
+    pub opacity: f32,
+    pub tint_color: Option<Color>,
+    pub properties: Properties,
+    pub layer_type: LayerDataType,
+}
+
+impl LayerData {
+    pub(crate) fn new(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
+        attrs: Vec<OwnedAttribute>,
+        tag: LayerTag,
+        infinite: bool,
+        map_path: &Path,
+        tilesets: &[MapTilesetGid],
+    ) -> Result<Self, TiledError> {
+        let (
+            (opacity, tint_color, visible, offset_x, offset_y, parallax_x, parallax_y, name, id),
+            (),
+        ) = get_attrs!(
+            attrs,
+            optionals: [
+                ("opacity", opacity, |v:String| v.parse().ok()),
+                ("tintcolor", tint_color, |v:String| v.parse().ok()),
+                ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)),
+                ("offsetx", offset_x, |v:String| v.parse().ok()),
+                ("offsety", offset_y, |v:String| v.parse().ok()),
+                ("parallaxx", parallax_x, |v:String| v.parse().ok()),
+                ("parallaxy", parallax_y, |v:String| v.parse().ok()),
+                ("name", name, |v| Some(v)),
+                ("id", id, |v:String| v.parse().ok()),
+            ],
+            required: [
+            ],
+
+            TiledError::MalformedAttributes("layer parsing error, no id attribute found".to_string())
+        );
+
+        let (ty, properties) = match tag {
+            LayerTag::TileLayer => {
+                let (ty, properties) = TileLayerData::new(parser, attrs, infinite, tilesets)?;
+                (LayerDataType::TileLayer(ty), properties)
+            }
+            LayerTag::ObjectLayer => {
+                let (ty, properties) = ObjectLayerData::new(parser, attrs, Some(tilesets))?;
+                (LayerDataType::ObjectLayer(ty), properties)
+            }
+            LayerTag::ImageLayer => {
+                let (ty, properties) = ImageLayerData::new(parser, map_path)?;
+                (LayerDataType::ImageLayer(ty), properties)
+            }
+        };
+
+        Ok(Self {
+            visible: visible.unwrap_or(true),
+            offset_x: offset_x.unwrap_or(0.0),
+            offset_y: offset_y.unwrap_or(0.0),
+            parallax_x: parallax_x.unwrap_or(1.0),
+            parallax_y: parallax_y.unwrap_or(1.0),
+            opacity: opacity.unwrap_or(1.0),
+            tint_color,
+            name: name.unwrap_or_default(),
+            id: id.unwrap_or(0),
+            properties,
+            layer_type: ty,
+        })
+    }
+}
+
+pub type Layer<'map> = MapWrapper<'map, LayerData>;
+
+impl<'map> Layer<'map> {
+    /// Get the layer's type.
+    pub fn layer_type(&self) -> LayerType<'map> {
+        LayerType::new(self.map(), &self.data().layer_type)
+    }
+}
+
+pub enum LayerType<'map> {
+    TileLayer(TileLayer<'map>),
+    ObjectLayer(ObjectLayer<'map>),
+    ImageLayer(ImageLayer<'map>),
+    // TODO: Support group layers
+}
+
+impl<'map> LayerType<'map> {
+    fn new(map: &'map Map, data: &'map LayerDataType) -> Self {
+        match data {
+            LayerDataType::TileLayer(data) => Self::TileLayer(TileLayer::new(map, data)),
+            LayerDataType::ObjectLayer(data) => Self::ObjectLayer(ObjectLayer::new(map, data)),
+            LayerDataType::ImageLayer(data) => Self::ImageLayer(ImageLayer::new(map, data)),
+        }
+    }
+}
diff --git a/src/layers/object.rs b/src/layers/object.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dbae3e8b4c70f218891bb56d244beb0e1640d40e
--- /dev/null
+++ b/src/layers/object.rs
@@ -0,0 +1,59 @@
+use std::collections::HashMap;
+
+use xml::attribute::OwnedAttribute;
+
+use crate::{
+    parse_properties,
+    util::{get_attrs, parse_tag, XmlEventResult},
+    Color, MapTilesetGid, MapWrapper, Object, ObjectData, Properties, TiledError,
+};
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct ObjectLayerData {
+    pub objects: Vec<ObjectData>,
+    pub colour: Option<Color>,
+}
+
+impl ObjectLayerData {
+    /// If it is known that there are no objects with tile images in it (i.e. collision data)
+    /// then we can pass in [`None`] as the tilesets
+    pub(crate) fn new(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
+        attrs: Vec<OwnedAttribute>,
+        tilesets: Option<&[MapTilesetGid]>,
+    ) -> Result<(ObjectLayerData, Properties), TiledError> {
+        let (c, ()) = get_attrs!(
+            attrs,
+            optionals: [
+                ("color", colour, |v:String| v.parse().ok()),
+            ],
+            required: [],
+            // this error should never happen since there are no required attrs
+            TiledError::MalformedAttributes("object group parsing error".to_string())
+        );
+        let mut objects = Vec::new();
+        let mut properties = HashMap::new();
+        parse_tag!(parser, "objectgroup", {
+            "object" => |attrs| {
+                objects.push(ObjectData::new(parser, attrs, tilesets)?);
+                Ok(())
+            },
+            "properties" => |_| {
+                properties = parse_properties(parser)?;
+                Ok(())
+            },
+        });
+        Ok((ObjectLayerData { objects, colour: c }, properties))
+    }
+}
+
+pub type ObjectLayer<'map> = MapWrapper<'map, ObjectLayerData>;
+
+impl<'map> ObjectLayer<'map> {
+    pub fn get_object(&self, idx: usize) -> Option<Object<'map>> {
+        self.data()
+            .objects
+            .get(idx)
+            .map(|data| Object::new(self.map(), data))
+    }
+}
diff --git a/src/layers/tile/finite.rs b/src/layers/tile/finite.rs
new file mode 100644
index 0000000000000000000000000000000000000000..55e9eb0e5d20cbd2ea9056bcef146c233fcfaaea
--- /dev/null
+++ b/src/layers/tile/finite.rs
@@ -0,0 +1,71 @@
+use xml::attribute::OwnedAttribute;
+
+use crate::{
+    util::{get_attrs, XmlEventResult},
+    LayerTileData, MapTilesetGid, TiledError,
+};
+
+use super::util::parse_data_line;
+
+#[derive(PartialEq, Clone, Default)]
+pub struct FiniteTileLayerData {
+    width: u32,
+    height: u32,
+    /// The tiles are arranged in rows.
+    pub(crate) tiles: Vec<Option<LayerTileData>>,
+}
+
+impl std::fmt::Debug for FiniteTileLayerData {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("FiniteTileLayerData")
+            .field("width", &self.width)
+            .field("height", &self.height)
+            .finish()
+    }
+}
+
+impl FiniteTileLayerData {
+    pub(crate) fn new(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
+        attrs: Vec<OwnedAttribute>,
+        width: u32,
+        height: u32,
+        tilesets: &[MapTilesetGid],
+    ) -> Result<Self, TiledError> {
+        let ((e, c), ()) = get_attrs!(
+            attrs,
+            optionals: [
+                ("encoding", encoding, |v| Some(v)),
+                ("compression", compression, |v| Some(v)),
+            ],
+            required: [],
+            TiledError::MalformedAttributes("data must have an encoding and a compression".to_string())
+        );
+
+        let tiles = parse_data_line(e, c, parser, tilesets)?;
+
+        Ok(Self {
+            width,
+            height,
+            tiles,
+        })
+    }
+
+    pub(crate) fn get_tile(&self, x: usize, y: usize) -> Option<&LayerTileData> {
+        if x < self.width as usize && y < self.height as usize {
+            self.tiles[x + y * self.width as usize].as_ref()
+        } else {
+            None
+        }
+    }
+
+    /// Get the tile layer's width in tiles.
+    pub fn width(&self) -> u32 {
+        self.width
+    }
+
+    /// Get the tile layer's height in tiles.
+    pub fn height(&self) -> u32 {
+        self.height
+    }
+}
diff --git a/src/layers/tile/infinite.rs b/src/layers/tile/infinite.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1a50aac8e37753f6f2853cc0bf8b0fda01bf2ca5
--- /dev/null
+++ b/src/layers/tile/infinite.rs
@@ -0,0 +1,91 @@
+use std::collections::HashMap;
+
+use xml::attribute::OwnedAttribute;
+
+use crate::{
+    util::{get_attrs, parse_tag, XmlEventResult},
+    LayerTileData, MapTilesetGid, TiledError,
+};
+
+use super::util::parse_data_line;
+
+#[derive(PartialEq, Clone)]
+pub struct InfiniteTileLayerData {
+    pub chunks: HashMap<(i32, i32), Chunk>,
+}
+
+impl std::fmt::Debug for InfiniteTileLayerData {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("InfiniteTileLayerData").finish()
+    }
+}
+
+impl InfiniteTileLayerData {
+    pub(crate) fn new(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
+        attrs: Vec<OwnedAttribute>,
+        tilesets: &[MapTilesetGid],
+    ) -> Result<Self, TiledError> {
+        let ((e, c), ()) = get_attrs!(
+            attrs,
+            optionals: [
+                ("encoding", encoding, |v| Some(v)),
+                ("compression", compression, |v| Some(v)),
+            ],
+            required: [],
+            TiledError::MalformedAttributes("data must have an encoding and a compression".to_string())
+        );
+
+        let mut chunks = HashMap::<(i32, i32), Chunk>::new();
+        parse_tag!(parser, "data", {
+            "chunk" => |attrs| {
+                let chunk = Chunk::new(parser, attrs, e.clone(), c.clone(), tilesets)?;
+                chunks.insert((chunk.x, chunk.y), chunk);
+                Ok(())
+            }
+        });
+
+        Ok(Self { chunks })
+    }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Chunk {
+    pub x: i32,
+    pub y: i32,
+    pub width: u32,
+    pub height: u32,
+    tiles: Vec<Option<LayerTileData>>,
+}
+
+impl Chunk {
+    pub(crate) fn new(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
+        attrs: Vec<OwnedAttribute>,
+        encoding: Option<String>,
+        compression: Option<String>,
+        tilesets: &[MapTilesetGid],
+    ) -> Result<Chunk, TiledError> {
+        let ((), (x, y, width, height)) = get_attrs!(
+            attrs,
+            optionals: [],
+            required: [
+                ("x", x, |v: String| v.parse().ok()),
+                ("y", y, |v: String| v.parse().ok()),
+                ("width", width, |v: String| v.parse().ok()),
+                ("height", height, |v: String| v.parse().ok()),
+            ],
+            TiledError::MalformedAttributes("layer must have a name".to_string())
+        );
+
+        let tiles = parse_data_line(encoding, compression, parser, tilesets)?;
+
+        Ok(Chunk {
+            x,
+            y,
+            width,
+            height,
+            tiles,
+        })
+    }
+}
diff --git a/src/layers/tile/mod.rs b/src/layers/tile/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..49c06b0440a255bb93f5597adbf6ac88bcee8aef
--- /dev/null
+++ b/src/layers/tile/mod.rs
@@ -0,0 +1,148 @@
+use std::collections::HashMap;
+
+use xml::attribute::OwnedAttribute;
+
+use crate::{
+    parse_properties,
+    util::{get_attrs, parse_tag, XmlEventResult},
+    Gid, Map, MapTilesetGid, MapWrapper, Properties, Tile, TileId, TiledError, Tileset,
+};
+
+mod finite;
+mod infinite;
+mod util;
+
+pub use finite::*;
+pub use infinite::*;
+
+/// Stores the internal tile gid about a layer tile, along with how it is flipped.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub(crate) struct LayerTileData {
+    /// The index of the tileset this tile's in, relative to the tile's map.
+    pub(crate) tileset_index: usize,
+    /// The local ID of the tile in the tileset it's in.
+    pub(crate) id: TileId,
+    pub flip_h: bool,
+    pub flip_v: bool,
+    pub flip_d: bool,
+}
+
+impl LayerTileData {
+    const FLIPPED_HORIZONTALLY_FLAG: u32 = 0x80000000;
+    const FLIPPED_VERTICALLY_FLAG: u32 = 0x40000000;
+    const FLIPPED_DIAGONALLY_FLAG: u32 = 0x20000000;
+    const ALL_FLIP_FLAGS: u32 = Self::FLIPPED_HORIZONTALLY_FLAG
+        | Self::FLIPPED_VERTICALLY_FLAG
+        | Self::FLIPPED_DIAGONALLY_FLAG;
+
+    /// Creates a new [`LayerTileData`] from a [`GID`] plus its flipping bits.
+    pub(crate) fn from_bits(bits: u32, tilesets: &[MapTilesetGid]) -> Option<Self> {
+        let flags = bits & Self::ALL_FLIP_FLAGS;
+        let gid = Gid(bits & !Self::ALL_FLIP_FLAGS);
+        let flip_d = flags & Self::FLIPPED_DIAGONALLY_FLAG == Self::FLIPPED_DIAGONALLY_FLAG; // Swap x and y axis (anti-diagonally) [flips over y = -x line]
+        let flip_h = flags & Self::FLIPPED_HORIZONTALLY_FLAG == Self::FLIPPED_HORIZONTALLY_FLAG; // Flip tile over y axis
+        let flip_v = flags & Self::FLIPPED_VERTICALLY_FLAG == Self::FLIPPED_VERTICALLY_FLAG; // Flip tile over x axis
+
+        if gid == Gid::EMPTY {
+            None
+        } else {
+            let (tileset_index, tileset) = crate::util::get_tileset_for_gid(tilesets, gid)?;
+            let id = gid.0 - tileset.first_gid.0;
+
+            Some(Self {
+                tileset_index,
+                id,
+                flip_h,
+                flip_v,
+                flip_d,
+            })
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum TileLayerData {
+    Finite(FiniteTileLayerData),
+    Infinite(InfiniteTileLayerData),
+}
+
+impl TileLayerData {
+    pub(crate) fn new(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
+        attrs: Vec<OwnedAttribute>,
+        infinite: bool,
+        tilesets: &[MapTilesetGid],
+    ) -> Result<(Self, Properties), TiledError> {
+        let ((), (width, height)) = get_attrs!(
+            attrs,
+            optionals: [
+            ],
+            required: [
+                ("width", width, |v: String| v.parse().ok()),
+                ("height", height, |v: String| v.parse().ok()),
+            ],
+            TiledError::MalformedAttributes("layer parsing error, width and height attributes required".to_string())
+        );
+        let mut result = Self::Finite(Default::default());
+        let mut properties = HashMap::new();
+        parse_tag!(parser, "layer", {
+            "data" => |attrs| {
+                if infinite {
+                    result = Self::Infinite(InfiniteTileLayerData::new(parser, attrs, tilesets)?);
+                } else {
+                    result = Self::Finite(FiniteTileLayerData::new(parser, attrs, width, height, tilesets)?);
+                }
+                Ok(())
+            },
+            "properties" => |_| {
+                properties = parse_properties(parser)?;
+                Ok(())
+            },
+        });
+
+        Ok((result, properties))
+    }
+
+    pub(crate) fn get_tile(&self, x: usize, y: usize) -> Option<&LayerTileData> {
+        match &self {
+            Self::Finite(finite) => finite.get_tile(x, y),
+            Self::Infinite(_) => todo!("Getting tiles from infinite layers"),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct LayerTile<'map> {
+    pub tileset: &'map Tileset,
+    pub id: TileId,
+    pub flip_h: bool,
+    pub flip_v: bool,
+    pub flip_d: bool,
+}
+
+impl<'map> LayerTile<'map> {
+    pub(crate) fn from_data(data: &LayerTileData, map: &'map Map) -> Self {
+        Self {
+            tileset: &*map.tilesets()[data.tileset_index],
+            id: data.id,
+            flip_h: data.flip_h,
+            flip_v: data.flip_v,
+            flip_d: data.flip_d,
+        }
+    }
+
+    /// Get a reference to the layer tile's referenced tile, if it exists.
+    pub fn get_tile(&self) -> Option<&'map Tile> {
+        self.tileset.get_tile(self.id)
+    }
+}
+
+pub type TileLayer<'map> = MapWrapper<'map, TileLayerData>;
+
+impl<'map> TileLayer<'map> {
+    pub fn get_tile(&self, x: usize, y: usize) -> Option<LayerTile> {
+        self.data()
+            .get_tile(x, y)
+            .and_then(|data| Some(LayerTile::from_data(data, self.map())))
+    }
+}
diff --git a/src/layers/tile/util.rs b/src/layers/tile/util.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b3c3a00ee4c68faf057b3315411284b775e8571c
--- /dev/null
+++ b/src/layers/tile/util.rs
@@ -0,0 +1,140 @@
+use std::io::{BufReader, Read};
+
+use xml::reader::XmlEvent;
+
+use crate::{util::XmlEventResult, LayerTileData, MapTilesetGid, TiledError};
+
+pub(crate) fn parse_data_line(
+    encoding: Option<String>,
+    compression: Option<String>,
+    parser: &mut impl Iterator<Item = XmlEventResult>,
+    tilesets: &[MapTilesetGid],
+) -> Result<Vec<Option<LayerTileData>>, TiledError> {
+    match (encoding, compression) {
+        (None, None) => {
+            return Err(TiledError::Other(
+                "XML format is currently not supported".to_string(),
+            ))
+        }
+        (Some(e), None) => match e.as_ref() {
+            "base64" => return parse_base64(parser).map(|v| convert_to_tiles(&v, tilesets)),
+            "csv" => return decode_csv(parser, tilesets),
+            e => return Err(TiledError::Other(format!("Unknown encoding format {}", e))),
+        },
+        (Some(e), Some(c)) => match (e.as_ref(), c.as_ref()) {
+            ("base64", "zlib") => {
+                return parse_base64(parser)
+                    .and_then(decode_zlib)
+                    .map(|v| convert_to_tiles(&v, tilesets))
+            }
+            ("base64", "gzip") => {
+                return parse_base64(parser)
+                    .and_then(decode_gzip)
+                    .map(|v| convert_to_tiles(&v, tilesets))
+            }
+            #[cfg(feature = "zstd")]
+            ("base64", "zstd") => {
+                return parse_base64(parser)
+                    .and_then(decode_zstd)
+                    .map(|v| convert_to_tiles(&v, tilesets))
+            }
+            (e, c) => {
+                return Err(TiledError::Other(format!(
+                    "Unknown combination of {} encoding and {} compression",
+                    e, c
+                )))
+            }
+        },
+        _ => return Err(TiledError::Other("Missing encoding format".to_string())),
+    };
+}
+
+fn parse_base64(parser: &mut impl Iterator<Item = XmlEventResult>) -> Result<Vec<u8>, TiledError> {
+    while let Some(next) = parser.next() {
+        match next.map_err(TiledError::XmlDecodingError)? {
+            XmlEvent::Characters(s) => {
+                return base64::decode(s.trim().as_bytes()).map_err(TiledError::Base64DecodingError)
+            }
+            XmlEvent::EndElement { name, .. } => {
+                if name.local_name == "data" {
+                    return Ok(Vec::new());
+                }
+            }
+            _ => {}
+        }
+    }
+    Err(TiledError::PrematureEnd("Ran out of XML data".to_owned()))
+}
+
+fn decode_zlib(data: Vec<u8>) -> Result<Vec<u8>, TiledError> {
+    use libflate::zlib::Decoder;
+    let mut zd =
+        Decoder::new(BufReader::new(&data[..])).map_err(|e| TiledError::DecompressingError(e))?;
+    let mut data = Vec::new();
+    match zd.read_to_end(&mut data) {
+        Ok(_v) => {}
+        Err(e) => return Err(TiledError::DecompressingError(e)),
+    }
+    Ok(data)
+}
+
+fn decode_gzip(data: Vec<u8>) -> Result<Vec<u8>, TiledError> {
+    use libflate::gzip::Decoder;
+    let mut zd =
+        Decoder::new(BufReader::new(&data[..])).map_err(|e| TiledError::DecompressingError(e))?;
+
+    let mut data = Vec::new();
+    zd.read_to_end(&mut data)
+        .map_err(|e| TiledError::DecompressingError(e))?;
+    Ok(data)
+}
+
+fn decode_zstd(data: Vec<u8>) -> Result<Vec<u8>, TiledError> {
+    use std::io::Cursor;
+    use zstd::stream::read::Decoder;
+
+    let buff = Cursor::new(&data);
+    let mut zd = Decoder::with_buffer(buff).map_err(|e| TiledError::DecompressingError(e))?;
+
+    let mut data = Vec::new();
+    zd.read_to_end(&mut data)
+        .map_err(|e| TiledError::DecompressingError(e))?;
+    Ok(data)
+}
+
+fn decode_csv(
+    parser: &mut impl Iterator<Item = XmlEventResult>,
+    tilesets: &[MapTilesetGid],
+) -> Result<Vec<Option<LayerTileData>>, TiledError> {
+    while let Some(next) = parser.next() {
+        match next.map_err(TiledError::XmlDecodingError)? {
+            XmlEvent::Characters(s) => {
+                let tiles = s
+                    .split(',')
+                    .map(|v| v.trim().parse().unwrap())
+                    .map(|bits| LayerTileData::from_bits(bits, tilesets))
+                    .collect();
+                return Ok(tiles);
+            }
+            XmlEvent::EndElement { name, .. } => {
+                if name.local_name == "data" {
+                    return Ok(Vec::new());
+                }
+            }
+            _ => {}
+        }
+    }
+    Err(TiledError::PrematureEnd("Ran out of XML data".to_owned()))
+}
+
+fn convert_to_tiles(all: &Vec<u8>, tilesets: &[MapTilesetGid]) -> Vec<Option<LayerTileData>> {
+    let mut data = Vec::new();
+    for chunk in all.chunks_exact(4) {
+        let n = chunk[0] as u32
+            + ((chunk[1] as u32) << 8)
+            + ((chunk[2] as u32) << 16)
+            + ((chunk[3] as u32) << 24);
+        data.push(LayerTileData::from_bits(n, tilesets));
+    }
+    data
+}
diff --git a/src/lib.rs b/src/lib.rs
index ff45bb162112bc1d4f6a7c04bfc07f0847bff013..45a03f3d0bdfc2f24fbda962b25179b174dc8e18 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
 mod animation;
+mod cache;
 mod error;
 mod image;
 mod layers;
@@ -10,6 +11,7 @@ mod tileset;
 mod util;
 
 pub use animation::*;
+pub use cache::*;
 pub use error::*;
 pub use image::*;
 pub use layers::*;
diff --git a/src/map.rs b/src/map.rs
index 1cee0140bc62ef1e9e863b78bd9badedd20456df..dddb7d2da520c0d540cecbc60c407f24f89e2e56 100644
--- a/src/map.rs
+++ b/src/map.rs
@@ -1,17 +1,23 @@
-use std::{collections::HashMap, fmt, fs::File, io::Read, path::Path, str::FromStr};
+use std::{collections::HashMap, fmt, fs::File, io::Read, path::Path, rc::Rc, str::FromStr};
 
 use xml::{attribute::OwnedAttribute, reader::XmlEvent, EventReader};
 
 use crate::{
     error::{ParseTileError, TiledError},
-    layers::{Layer, LayerTag},
+    layers::{LayerData, LayerTag},
     properties::{parse_properties, Color, Properties},
     tileset::Tileset,
-    util::{get_attrs, parse_tag},
+    util::{get_attrs, parse_tag, XmlEventResult},
+    EmbeddedParseResultType, Layer, ResourceCache,
 };
 
+pub(crate) struct MapTilesetGid {
+    pub first_gid: Gid,
+    pub tileset: Rc<Tileset>,
+}
+
 /// All Tiled map files will be parsed into this. Holds all the layers and tilesets.
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone, Debug)]
 pub struct Map {
     /// The TMX format version this map was saved to.
     pub version: String,
@@ -24,10 +30,10 @@ pub struct Map {
     pub tile_width: u32,
     /// Tile height, in pixels.
     pub tile_height: u32,
-    /// The tilesets present in this map.
-    pub tilesets: Vec<Tileset>,
+    /// The tilesets present on this map.
+    tilesets: Vec<Rc<Tileset>>,
     /// The layers present in this map.
-    pub layers: Vec<Layer>,
+    layers: Vec<LayerData>,
     /// The custom properties of this map.
     pub properties: Properties,
     /// The background color of this map, if any.
@@ -41,10 +47,16 @@ impl Map {
     /// (e.g. Amethyst) simply hand over a byte stream (and file location) for parsing,
     /// in which case this function may be required.
     ///
-    /// The path is used for external dependencies such as tilesets or images, and may be skipped
-    /// if the map is fully embedded (Doesn't refer to external files). If a map *does* refer to
-    /// external files and a path is not given, the function will return [TiledError::SourceRequired].
-    pub fn parse_reader<R: Read>(reader: R, path: Option<&Path>) -> Result<Self, TiledError> {
+    /// The path is used for external dependencies such as tilesets or images. It is required.
+    /// If the map if fully embedded and doesn't refer to external files, you may input an arbitrary path;
+    /// the library won't read from the filesystem if it is not required to do so.
+    ///
+    /// The tileset cache is used to store and refer to any tilesets found along the way.
+    pub fn parse_reader<R: Read>(
+        reader: R,
+        path: impl AsRef<Path>,
+        cache: &mut impl ResourceCache,
+    ) -> Result<Self, TiledError> {
         let mut parser = EventReader::new(reader);
         loop {
             match parser.next().map_err(TiledError::XmlDecodingError)? {
@@ -52,7 +64,12 @@ impl Map {
                     name, attributes, ..
                 } => {
                     if name.local_name == "map" {
-                        return Self::parse_xml(&mut parser, attributes, path);
+                        return Self::parse_xml(
+                            &mut parser.into_iter(),
+                            attributes,
+                            path.as_ref(),
+                            cache,
+                        );
                     }
                 }
                 XmlEvent::EndDocument => {
@@ -67,16 +84,69 @@ impl Map {
 
     /// Parse a file hopefully containing a Tiled map and try to parse it.  All external
     /// files will be loaded relative to the path given.
-    pub fn parse_file(path: impl AsRef<Path>) -> Result<Self, TiledError> {
-        let file = File::open(path.as_ref())
+    ///
+    /// The tileset cache is used to store and refer to any tilesets found along the way.
+    pub fn parse_file(
+        path: impl AsRef<Path>,
+        cache: &mut impl ResourceCache,
+    ) -> Result<Self, TiledError> {
+        let reader = File::open(path.as_ref())
             .map_err(|_| TiledError::Other(format!("Map file not found: {:?}", path.as_ref())))?;
-        Self::parse_reader(file, Some(path.as_ref()))
+        Self::parse_reader(reader, path.as_ref(), cache)
+    }
+}
+
+impl Map {
+    /// Get a reference to the map's tilesets.
+    pub fn tilesets(&self) -> &[Rc<Tileset>] {
+        self.tilesets.as_ref()
+    }
+
+    /// Get an iterator over all the layers in the map in ascending order of their layer index.
+    pub fn layers(&self) -> LayerIter {
+        LayerIter::new(self)
     }
 
-    fn parse_xml<R: Read>(
-        parser: &mut EventReader<R>,
+    /// Returns the layer that has the specified index, if it exists.
+    pub fn get_layer(&self, index: usize) -> Option<Layer> {
+        self.layers.get(index).map(|data| Layer::new(self, data))
+    }
+}
+
+/// An iterator that iterates over all the layers in a map, obtained via [`Map::layers`].
+pub struct LayerIter<'map> {
+    map: &'map Map,
+    index: usize,
+}
+
+impl<'map> LayerIter<'map> {
+    fn new(map: &'map Map) -> Self {
+        Self { map, index: 0 }
+    }
+}
+
+impl<'map> Iterator for LayerIter<'map> {
+    type Item = Layer<'map>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let layer_data = self.map.layers.get(self.index)?;
+        self.index += 1;
+        Some(Layer::new(self.map, layer_data))
+    }
+}
+
+impl<'map> ExactSizeIterator for LayerIter<'map> {
+    fn len(&self) -> usize {
+        self.map.layers.len() - self.index
+    }
+}
+
+impl Map {
+    fn parse_xml(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
         attrs: Vec<OwnedAttribute>,
-        map_path: Option<&Path>,
+        map_path: &Path,
+        cache: &mut impl ResourceCache,
     ) -> Result<Map, TiledError> {
         let ((c, infinite), (v, o, w, h, tw, th)) = get_attrs!(
             attrs,
@@ -96,33 +166,71 @@ impl Map {
         );
 
         let infinite = infinite.unwrap_or(false);
-        let source_path = map_path.and_then(|p| p.parent());
 
-        let mut tilesets = Vec::new();
+        // We can only parse sequentally, but tilesets are guaranteed to appear before layers.
+        // So we can pass in tileset data to layer construction without worrying about unfinished
+        // data usage.
         let mut layers = Vec::new();
         let mut properties = HashMap::new();
+        let mut tilesets = Vec::new();
+
         parse_tag!(parser, "map", {
             "tileset" => |attrs| {
-                tilesets.push(Tileset::parse_xml(parser, attrs, source_path)?);
+                let res = Tileset::parse_xml_in_map(parser, attrs, map_path)?;
+                match res.result_type {
+                    EmbeddedParseResultType::ExternalReference { tileset_path } => {
+                        let file = File::open(&tileset_path).map_err(|err| TiledError::CouldNotOpenFile{path: tileset_path.clone(), err })?;
+                        let tileset = cache.get_or_try_insert_tileset_with(tileset_path.clone(), || Tileset::new_external(file, Some(&tileset_path)))?;
+                        tilesets.push(MapTilesetGid{first_gid: res.first_gid, tileset});
+                    }
+                    EmbeddedParseResultType::Embedded { tileset } => {
+                        tilesets.push(MapTilesetGid{first_gid: res.first_gid, tileset: Rc::new(tileset)});
+                    },
+                };
                 Ok(())
             },
             "layer" => |attrs| {
-                layers.push(Layer::new(parser, attrs, LayerTag::TileLayer, infinite, source_path)?);
+                layers.push(LayerData::new(
+                    parser,
+                    attrs,
+                    LayerTag::TileLayer,
+                    infinite,
+                    map_path,
+                    &tilesets,
+                )?);
                 Ok(())
             },
             "imagelayer" => |attrs| {
-                layers.push(Layer::new(parser, attrs, LayerTag::ImageLayer, infinite, source_path)?);
+                layers.push(LayerData::new(
+                    parser,
+                    attrs,
+                    LayerTag::ImageLayer,
+                    infinite,
+                    map_path,
+                    &tilesets,
+                )?);
                 Ok(())
             },
-            "properties" => |_| {
-                properties = parse_properties(parser)?;
+            "objectgroup" => |attrs| {
+                layers.push(LayerData::new(
+                    parser,
+                    attrs,
+                    LayerTag::ObjectLayer,
+                    infinite,
+                    map_path,
+                    &tilesets,
+                )?);
                 Ok(())
             },
-            "objectgroup" => |attrs| {
-                layers.push(Layer::new(parser, attrs, LayerTag::ObjectLayer, infinite, source_path)?);
+            "properties" => |_| {
+                properties = parse_properties(parser)?;
                 Ok(())
             },
         });
+
+        // We do not need first GIDs any more
+        let tilesets = tilesets.into_iter().map(|ts| ts.tileset).collect();
+
         Ok(Map {
             version: v,
             orientation: o,
@@ -137,19 +245,6 @@ impl Map {
             infinite,
         })
     }
-
-    /// This function will return the correct Tileset given a GID.
-    pub fn tileset_by_gid(&self, gid: u32) -> Option<&Tileset> {
-        let mut maximum_gid: i32 = -1;
-        let mut maximum_ts = None;
-        for tileset in self.tilesets.iter() {
-            if tileset.first_gid as i32 > maximum_gid && tileset.first_gid <= gid {
-                maximum_gid = tileset.first_gid as i32;
-                maximum_ts = Some(tileset);
-            }
-        }
-        maximum_ts
-    }
 }
 
 /// Represents the way tiles are laid out in a map.
@@ -185,3 +280,51 @@ impl fmt::Display for Orientation {
         }
     }
 }
+
+/// A Tiled global tile ID.
+///
+/// These are used to identify tiles in a map. Since the map may have more than one tileset, an
+/// unique mapping is required to convert the tiles' local tileset ID to one which will work nicely
+/// even if there is more than one tileset.
+///
+/// Tiled also treats GID 0 as empty space, which means that the first tileset in the map will have
+/// a starting GID of 1.
+///
+/// See also: https://doc.mapeditor.org/en/latest/reference/global-tile-ids/
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub(crate) struct Gid(pub u32);
+
+impl Gid {
+    /// The GID representing an empty tile in the map.
+    #[allow(dead_code)]
+    pub const EMPTY: Gid = Gid(0);
+}
+
+/// A wrapper over a naive datatype that holds a reference to the parent map as well as the type's data.
+#[derive(Clone, PartialEq, Debug)]
+pub struct MapWrapper<'map, DataT>
+where
+    DataT: Clone + PartialEq + std::fmt::Debug,
+{
+    map: &'map Map,
+    data: &'map DataT,
+}
+
+impl<'map, DataT> MapWrapper<'map, DataT>
+where
+    DataT: Clone + PartialEq + std::fmt::Debug,
+{
+    pub(crate) fn new(map: &'map Map, data: &'map DataT) -> Self {
+        Self { map, data }
+    }
+
+    /// Get the wrapper's data.
+    pub fn data(&self) -> &'map DataT {
+        self.data
+    }
+
+    /// Get the wrapper's map.
+    pub fn map(&self) -> &'map Map {
+        self.map
+    }
+}
diff --git a/src/objects.rs b/src/objects.rs
index 6569f333f38a0626ec525e10dbda7b4499c66b20..5c4e1fb92366be7163a2069a6240850f3d678ebf 100644
--- a/src/objects.rs
+++ b/src/objects.rs
@@ -1,11 +1,12 @@
-use std::{collections::HashMap, io::Read};
+use std::collections::HashMap;
 
-use xml::{attribute::OwnedAttribute, EventReader};
+use xml::attribute::OwnedAttribute;
 
 use crate::{
     error::TiledError,
     properties::{parse_properties, Properties},
-    util::{get_attrs, parse_tag},
+    util::{get_attrs, parse_tag, XmlEventResult},
+    LayerTile, LayerTileData, MapTilesetGid, MapWrapper,
 };
 
 #[derive(Debug, PartialEq, Clone)]
@@ -18,9 +19,9 @@ pub enum ObjectShape {
 }
 
 #[derive(Debug, PartialEq, Clone)]
-pub struct Object {
+pub struct ObjectData {
     pub id: u32,
-    pub gid: u32,
+    tile: Option<LayerTileData>,
     pub name: String,
     pub obj_type: String,
     pub width: f32,
@@ -33,12 +34,15 @@ pub struct Object {
     pub properties: Properties,
 }
 
-impl Object {
-    pub(crate) fn new<R: Read>(
-        parser: &mut EventReader<R>,
+impl ObjectData {
+    /// If it is known that the object has no tile images in it (i.e. collision data)
+    /// then we can pass in [`None`] as the tilesets
+    pub(crate) fn new(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
         attrs: Vec<OwnedAttribute>,
-    ) -> Result<Object, TiledError> {
-        let ((id, gid, n, t, w, h, v, r), (x, y)) = get_attrs!(
+        tilesets: Option<&[MapTilesetGid]>,
+    ) -> Result<ObjectData, TiledError> {
+        let ((id, bits, n, t, w, h, v, r), (x, y)) = get_attrs!(
             attrs,
             optionals: [
                 ("id", id, |v:String| v.parse().ok()),
@@ -61,7 +65,7 @@ impl Object {
         let h = h.unwrap_or(0f32);
         let r = r.unwrap_or(0f32);
         let id = id.unwrap_or(0u32);
-        let gid = gid.unwrap_or(0u32);
+        let tile = bits.and_then(|bits| LayerTileData::from_bits(bits, tilesets?));
         let n = n.unwrap_or(String::new());
         let t = t.unwrap_or(String::new());
         let mut shape = None;
@@ -76,15 +80,15 @@ impl Object {
                 Ok(())
             },
             "polyline" => |attrs| {
-                shape = Some(Object::new_polyline(attrs)?);
+                shape = Some(ObjectData::new_polyline(attrs)?);
                 Ok(())
             },
             "polygon" => |attrs| {
-                shape = Some(Object::new_polygon(attrs)?);
+                shape = Some(ObjectData::new_polygon(attrs)?);
                 Ok(())
             },
             "point" => |_| {
-                shape = Some(Object::new_point(x, y)?);
+                shape = Some(ObjectData::new_point(x, y)?);
                 Ok(())
             },
             "properties" => |_| {
@@ -98,22 +102,24 @@ impl Object {
             height: h,
         });
 
-        Ok(Object {
-            id: id,
-            gid: gid,
+        Ok(ObjectData {
+            id,
+            tile,
             name: n.clone(),
             obj_type: t.clone(),
             width: w,
             height: h,
-            x: x,
-            y: y,
+            x,
+            y,
             rotation: r,
             visible: v,
-            shape: shape,
-            properties: properties,
+            shape,
+            properties,
         })
     }
+}
 
+impl ObjectData {
     fn new_polyline(attrs: Vec<OwnedAttribute>) -> Result<ObjectShape, TiledError> {
         let ((), s) = get_attrs!(
             attrs,
@@ -123,7 +129,7 @@ impl Object {
             ],
             TiledError::MalformedAttributes("A polyline must have points".to_string())
         );
-        let points = Object::parse_points(s)?;
+        let points = ObjectData::parse_points(s)?;
         Ok(ObjectShape::Polyline { points: points })
     }
 
@@ -136,7 +142,7 @@ impl Object {
             ],
             TiledError::MalformedAttributes("A polygon must have points".to_string())
         );
-        let points = Object::parse_points(s)?;
+        let points = ObjectData::parse_points(s)?;
         Ok(ObjectShape::Polygon { points: points })
     }
 
@@ -165,3 +171,14 @@ impl Object {
         Ok(points)
     }
 }
+
+pub type Object<'map> = MapWrapper<'map, ObjectData>;
+
+impl<'map> Object<'map> {
+    /// Returns the tile that the object is using as image, if any.
+    pub fn get_tile<'res: 'map>(&self) -> Option<LayerTile<'map>> {
+        self.data()
+            .tile
+            .map(|tile| LayerTile::from_data(&tile, self.map()))
+    }
+}
diff --git a/src/properties.rs b/src/properties.rs
index 949046c9d9054e63294287d447802dfbfcadbf4b..928b7f910429501d02f40eeb0ceb60b6278f76dc 100644
--- a/src/properties.rs
+++ b/src/properties.rs
@@ -1,10 +1,10 @@
-use std::{collections::HashMap, io::Read, str::FromStr};
+use std::{collections::HashMap, str::FromStr};
 
-use xml::{attribute::OwnedAttribute, reader::XmlEvent, EventReader};
+use xml::{attribute::OwnedAttribute, reader::XmlEvent};
 
 use crate::{
     error::TiledError,
-    util::{get_attrs, parse_tag},
+    util::{get_attrs, parse_tag, XmlEventResult},
 };
 
 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
@@ -106,8 +106,8 @@ impl PropertyValue {
 
 pub type Properties = HashMap<String, PropertyValue>;
 
-pub(crate) fn parse_properties<R: Read>(
-    parser: &mut EventReader<R>,
+pub(crate) fn parse_properties(
+    parser: &mut impl Iterator<Item = XmlEventResult>,
 ) -> Result<Properties, TiledError> {
     let mut p = HashMap::new();
     parse_tag!(parser, "properties", {
@@ -125,12 +125,14 @@ pub(crate) fn parse_properties<R: Read>(
             );
             let t = t.unwrap_or("string".into());
 
-            let v = match v_attr {
+            let v: String = match v_attr {
                 Some(val) => val,
                 None => {
                     // if the "value" attribute was missing, might be a multiline string
-                    match parser.next().map_err(TiledError::XmlDecodingError)? {
-                        XmlEvent::Characters(s) => Ok(s),
+                    match parser.next() {
+                        Some(Ok(XmlEvent::Characters(s))) => Ok(s),
+                        Some(Err(err)) => Err(TiledError::XmlDecodingError(err)),
+                        None => unreachable!(), // EndDocument or error must come first
                         _ => Err(TiledError::MalformedAttributes(format!("property '{}' is missing a value", k))),
                     }?
                 }
diff --git a/src/tile.rs b/src/tile.rs
index a6d623b24b8da15555c9290e18c299016b570c2a..0493fb508c21eaae75f294ee7749f87fc4a31ddb 100644
--- a/src/tile.rs
+++ b/src/tile.rs
@@ -1,33 +1,34 @@
-use std::{collections::HashMap, io::Read, path::Path};
+use std::{collections::HashMap, path::Path};
 
-use xml::{attribute::OwnedAttribute, EventReader};
+use xml::attribute::OwnedAttribute;
 
 use crate::{
     animation::Frame,
     error::TiledError,
     image::Image,
-    layers::ObjectLayer,
+    layers::ObjectLayerData,
     properties::{parse_properties, Properties},
-    util::{get_attrs, parse_animation, parse_tag},
+    util::{get_attrs, parse_animation, parse_tag, XmlEventResult},
 };
 
-#[derive(Debug, PartialEq, Clone)]
+pub type TileId = u32;
+
+#[derive(Debug, PartialEq, Clone, Default)]
 pub struct Tile {
-    pub id: u32,
     pub image: Option<Image>,
     pub properties: Properties,
-    pub collision: Option<ObjectLayer>,
+    pub collision: Option<ObjectLayerData>,
     pub animation: Option<Vec<Frame>>,
     pub tile_type: Option<String>,
     pub probability: f32,
 }
 
 impl Tile {
-    pub(crate) fn new<R: Read>(
-        parser: &mut EventReader<R>,
+    pub(crate) fn new(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
         attrs: Vec<OwnedAttribute>,
         path_relative_to: Option<&Path>,
-    ) -> Result<Tile, TiledError> {
+    ) -> Result<(TileId, Tile), TiledError> {
         let ((tile_type, probability), id) = get_attrs!(
             attrs,
             optionals: [
@@ -46,7 +47,7 @@ impl Tile {
         let mut animation = None;
         parse_tag!(parser, "tile", {
             "image" => |attrs| {
-                image = Some(Image::new(parser, attrs, path_relative_to.ok_or(TiledError::SourceRequired{object_to_parse: "Image".to_owned()})?)?);
+                image = Some(Image::new(parser, attrs, path_relative_to.ok_or(TiledError::SourceRequired{object_to_parse:"Image".to_owned()})?)?);
                 Ok(())
             },
             "properties" => |_| {
@@ -54,7 +55,7 @@ impl Tile {
                 Ok(())
             },
             "objectgroup" => |attrs| {
-                objectgroup = Some(ObjectLayer::new(parser, attrs)?.0);
+                objectgroup = Some(ObjectLayerData::new(parser, attrs, None)?.0);
                 Ok(())
             },
             "animation" => |_| {
@@ -62,14 +63,16 @@ impl Tile {
                 Ok(())
             },
         });
-        Ok(Tile {
+        Ok((
             id,
-            image,
-            properties,
-            collision: objectgroup,
-            animation,
-            tile_type,
-            probability: probability.unwrap_or(1.0),
-        })
+            Tile {
+                image,
+                properties,
+                collision: objectgroup,
+                animation,
+                tile_type,
+                probability: probability.unwrap_or(1.0),
+            },
+        ))
     }
 }
diff --git a/src/tileset.rs b/src/tileset.rs
index 2b120ecd9b53f4c45085cb9d75c29a0ac7571f45..1efcb5db8535e96fa45c9a7a1115ba0aacb7ec15 100644
--- a/src/tileset.rs
+++ b/src/tileset.rs
@@ -1,5 +1,4 @@
 use std::collections::HashMap;
-use std::fs::File;
 use std::io::Read;
 use std::path::{Path, PathBuf};
 
@@ -11,19 +10,17 @@ use crate::error::TiledError;
 use crate::image::Image;
 use crate::properties::{parse_properties, Properties};
 use crate::tile::Tile;
-use crate::util::*;
+use crate::{util::*, Gid};
 
 /// A tileset, usually the tilesheet image.
 #[derive(Debug, PartialEq, Clone)]
 pub struct Tileset {
-    /// The GID of the first tile stored.
-    pub first_gid: u32,
     pub name: String,
     pub tile_width: u32,
     pub tile_height: u32,
     pub spacing: u32,
     pub margin: u32,
-    pub tilecount: Option<u32>,
+    pub tilecount: u32,
     pub columns: u32,
 
     /// A tileset can either:
@@ -34,70 +31,60 @@ pub struct Tileset {
     /// - Source: [tiled issue #2117](https://github.com/mapeditor/tiled/issues/2117)
     /// - Source: [`columns` documentation](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#tileset)
     pub image: Option<Image>,
-    pub tiles: Vec<Tile>,
+
+    /// All the tiles present in this tileset, indexed by their local IDs.
+    pub tiles: HashMap<u32, Tile>,
+
+    /// The custom properties of the tileset.
     pub properties: Properties,
 
     /// Where this tileset was loaded from.
-    /// If fully embedded (loaded with path = `None`), this will return `None`.
+    /// If fully embedded, this will return `None`.
     pub source: Option<PathBuf>,
 }
 
+pub(crate) enum EmbeddedParseResultType {
+    ExternalReference { tileset_path: PathBuf },
+    Embedded { tileset: Tileset },
+}
+
+pub(crate) struct EmbeddedParseResult {
+    pub first_gid: Gid,
+    pub result_type: EmbeddedParseResultType,
+}
+
 /// Internal structure for holding mid-parse information.
 struct TilesetProperties {
     spacing: Option<u32>,
     margin: Option<u32>,
-    tilecount: Option<u32>,
+    tilecount: u32,
     columns: Option<u32>,
-    first_gid: u32,
     name: String,
     tile_width: u32,
     tile_height: u32,
+    /// The path all non-absolute paths are relative to.
     path_relative_to: Option<PathBuf>,
     source: Option<PathBuf>,
 }
 
 impl Tileset {
     /// Parse a buffer hopefully containing the contents of a Tiled tileset.
-    ///
-    /// External tilesets do not have a firstgid attribute.  That lives in the
-    /// map. You must pass in `first_gid`.  If you do not need to use gids for anything,
-    /// passing in 1 will work fine.
-    pub fn parse<R: Read>(reader: R, first_gid: u32) -> Result<Self, TiledError> {
-        Tileset::new_external(reader, first_gid, None)
+    pub fn parse<R: Read>(reader: R) -> Result<Self, TiledError> {
+        Tileset::new_external(reader, None)
     }
 
     /// Parse a buffer hopefully containing the contents of a Tiled tileset.
-    ///
-    /// External tilesets do not have a firstgid attribute.  That lives in the
-    /// map. You must pass in `first_gid`.  If you do not need to use gids for anything,
-    /// passing in 1 will work fine.
-    pub fn parse_with_path<R: Read>(
-        reader: R,
-        first_gid: u32,
-        path: impl AsRef<Path>,
-    ) -> Result<Self, TiledError> {
-        Tileset::new_external(reader, first_gid, Some(path.as_ref()))
+    pub fn parse_with_path<R: Read>(reader: R, path: impl AsRef<Path>) -> Result<Self, TiledError> {
+        Tileset::new_external(reader, Some(path.as_ref()))
     }
 
-    pub(crate) fn parse_xml<R: Read>(
-        parser: &mut EventReader<R>,
-        attrs: Vec<OwnedAttribute>,
-        path_relative_to: Option<&Path>,
-    ) -> Result<Tileset, TiledError> {
-        Tileset::parse_xml_embedded(parser, &attrs, path_relative_to).or_else(|err| {
-            if matches!(err, TiledError::MalformedAttributes(_)) {
-                Tileset::parse_xml_reference(&attrs, path_relative_to)
-            } else {
-                Err(err)
-            }
-        })
+    pub fn get_tile(&self, id: u32) -> Option<&Tile> {
+        self.tiles.get(&id)
     }
+}
 
-    pub(crate) fn new_external<R: Read>(
-        file: R,
-        first_gid: u32,
-        path: Option<&Path>,
-    ) -> Result<Self, TiledError> {
+impl Tileset {
+    pub(crate) fn new_external<R: Read>(file: R, path: Option<&Path>) -> Result<Self, TiledError> {
         let mut tileset_parser = EventReader::new(file);
         loop {
             match tileset_parser
@@ -109,8 +96,7 @@ impl Tileset {
                 } => {
                     if name.local_name == "tileset" {
                         return Self::parse_external_tileset(
-                            first_gid,
-                            &mut tileset_parser,
+                            &mut tileset_parser.into_iter(),
                             &attributes,
                             path,
                         );
@@ -126,21 +112,37 @@ impl Tileset {
         }
     }
 
-    fn parse_xml_embedded<R: Read>(
-        parser: &mut EventReader<R>,
+    pub(crate) fn parse_xml_in_map(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
+        attrs: Vec<OwnedAttribute>,
+        map_path: &Path,
+    ) -> Result<EmbeddedParseResult, TiledError> {
+        let path_relative_to = map_path.parent();
+        Tileset::parse_xml_embedded(parser, &attrs, path_relative_to).or_else(|err| {
+            if matches!(err, TiledError::MalformedAttributes(_)) {
+                Tileset::parse_xml_reference(&attrs, path_relative_to)
+            } else {
+                Err(err)
+            }
+        })
+    }
+
+    /// Returns both the tileset and its first gid in the corresponding map.
+    fn parse_xml_embedded(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
         attrs: &Vec<OwnedAttribute>,
         path_relative_to: Option<&Path>,
-    ) -> Result<Tileset, TiledError> {
-        let ((spacing, margin, tilecount, columns), (first_gid, name, tile_width, tile_height)) = get_attrs!(
+    ) -> Result<EmbeddedParseResult, TiledError> {
+        let ((spacing, margin, columns), (tilecount, first_gid, name, tile_width, tile_height)) = get_attrs!(
            attrs,
            optionals: [
                 ("spacing", spacing, |v:String| v.parse().ok()),
                 ("margin", margin, |v:String| v.parse().ok()),
-                ("tilecount", tilecount, |v:String| v.parse().ok()),
                 ("columns", columns, |v:String| v.parse().ok()),
             ],
            required: [
-                ("firstgid", first_gid, |v:String| v.parse().ok()),
+                ("tilecount", tilecount, |v:String| v.parse().ok()),
+                ("firstgid", first_gid, |v:String| v.parse().ok().map(|n| Gid(n))),
                 ("name", name, |v| Some(v)),
                 ("tilewidth", width, |v:String| v.parse().ok()),
                 ("tileheight", height, |v:String| v.parse().ok()),
@@ -159,21 +161,24 @@ impl Tileset {
                 tilecount,
                 tile_height,
                 tile_width,
-                first_gid,
                 source: None,
             },
         )
+        .map(|tileset| EmbeddedParseResult {
+            first_gid,
+            result_type: EmbeddedParseResultType::Embedded { tileset },
+        })
     }
 
     fn parse_xml_reference(
         attrs: &Vec<OwnedAttribute>,
         path_relative_to: Option<&Path>,
-    ) -> Result<Tileset, TiledError> {
+    ) -> Result<EmbeddedParseResult, TiledError> {
         let ((), (first_gid, source)) = get_attrs!(
             attrs,
             optionals: [],
             required: [
-                ("firstgid", first_gid, |v:String| v.parse().ok()),
+                ("firstgid", first_gid, |v:String| v.parse().ok().map(|n| Gid(n))),
                 ("source", name, |v| Some(v)),
             ],
             TiledError::MalformedAttributes("Tileset reference must have a firstgid and source with correct types".to_string())
@@ -184,35 +189,32 @@ impl Tileset {
                 object_to_parse: "Tileset".to_string(),
             })?
             .join(source);
-        let file = File::open(&tileset_path).map_err(|_| {
-            TiledError::Other(format!(
-                "External tileset file not found: {:?}",
-                tileset_path
-            ))
-        })?;
-        Tileset::new_external(file, first_gid, Some(&tileset_path))
+
+        Ok(EmbeddedParseResult {
+            first_gid,
+            result_type: EmbeddedParseResultType::ExternalReference { tileset_path },
+        })
     }
 
-    fn parse_external_tileset<R: Read>(
-        first_gid: u32,
-        parser: &mut EventReader<R>,
+    fn parse_external_tileset(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
         attrs: &Vec<OwnedAttribute>,
         path: Option<&Path>,
     ) -> Result<Tileset, TiledError> {
-        let ((spacing, margin, tilecount, columns), (name, tile_width, tile_height)) = get_attrs!(
+        let ((spacing, margin, columns), (tilecount, name, tile_width, tile_height)) = get_attrs!(
             attrs,
             optionals: [
                 ("spacing", spacing, |v:String| v.parse().ok()),
                 ("margin", margin, |v:String| v.parse().ok()),
-                ("tilecount", tilecount, |v:String| v.parse().ok()),
                 ("columns", columns, |v:String| v.parse().ok()),
             ],
             required: [
+                ("tilecount", tilecount, |v:String| v.parse().ok()),
                 ("name", name, |v| Some(v)),
                 ("tilewidth", width, |v:String| v.parse().ok()),
                 ("tileheight", height, |v:String| v.parse().ok()),
             ],
-            TiledError::MalformedAttributes("tileset must have a firstgid, name tile width and height with correct types".to_string())
+            TiledError::MalformedAttributes("tileset must have a name, tile width and height with correct types".to_string())
         );
 
         let source_path = path.and_then(|p| p.parent().map(Path::to_owned));
@@ -228,19 +230,19 @@ impl Tileset {
                 tilecount,
                 tile_height,
                 tile_width,
-                first_gid,
                 source: path.map(Path::to_owned),
             },
         )
     }
 
-    fn finish_parsing_xml<R: Read>(
-        parser: &mut EventReader<R>,
+    fn finish_parsing_xml(
+        parser: &mut impl Iterator<Item = XmlEventResult>,
         prop: TilesetProperties,
-    ) -> Result<Self, TiledError> {
+    ) -> Result<Tileset, TiledError> {
         let mut image = Option::None;
-        let mut tiles = Vec::new();
+        let mut tiles = HashMap::with_capacity(prop.tilecount as usize);
         let mut properties = HashMap::new();
+
         parse_tag!(parser, "tileset", {
             "image" => |attrs| {
                 image = Some(Image::new(parser, attrs, prop.path_relative_to.as_ref().ok_or(TiledError::SourceRequired{object_to_parse: "Image".to_string()})?)?);
@@ -251,20 +253,29 @@ impl Tileset {
                 Ok(())
             },
             "tile" => |attrs| {
-                tiles.push(Tile::new(parser, attrs, prop.path_relative_to.as_ref().and_then(|p| Some(p.as_path())))?);
+                let (id, tile) = Tile::new(parser, attrs, prop.path_relative_to.as_ref().and_then(|p| Some(p.as_path())))?;
+                tiles.insert(id, tile);
                 Ok(())
             },
         });
 
-        let (margin, spacing) = (prop.margin.unwrap_or(0), prop.spacing.unwrap_or(0));
+        // A tileset is considered an image collection tileset if there is no image attribute (because its tiles do).
+        let is_image_collection_tileset = image.is_none();
+
+        if !is_image_collection_tileset {
+            for tile_id in 0..prop.tilecount {
+                tiles.entry(tile_id).or_default();
+            }
+        }
 
+        let margin = prop.margin.unwrap_or(0);
+        let spacing = prop.spacing.unwrap_or(0);
         let columns = prop
             .columns
             .map(Ok)
             .unwrap_or_else(|| Self::calculate_columns(&image, prop.tile_width, margin, spacing))?;
 
         Ok(Tileset {
-            first_gid: prop.first_gid,
             name: prop.name,
             tile_width: prop.tile_width,
             tile_height: prop.tile_height,
diff --git a/src/util.rs b/src/util.rs
index 500cf3c8c091b66a1facfecd93f06b3b8cd37ee1..0eafe24679acfe2c7b8f421f52b3ab2c059dc274 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -29,8 +29,8 @@ macro_rules! get_attrs {
 /// that child. Closes the tag.
 macro_rules! parse_tag {
     ($parser:expr, $close_tag:expr, {$($open_tag:expr => $open_method:expr),* $(,)*}) => {
-        loop {
-            match $parser.next().map_err(TiledError::XmlDecodingError)? {
+        while let Some(next) = $parser.next() {
+            match next.map_err(TiledError::XmlDecodingError)? {
                 xml::reader::XmlEvent::StartElement {name, attributes, ..} => {
                     if false {}
                     $(else if name.local_name == $open_tag {
@@ -52,23 +52,14 @@ macro_rules! parse_tag {
     }
 }
 
-use std::{
-    collections::HashMap,
-    io::{BufReader, Read},
-};
-
 pub(crate) use get_attrs;
 pub(crate) use parse_tag;
-use xml::{attribute::OwnedAttribute, reader::XmlEvent, EventReader};
 
-use crate::{
-    animation::Frame,
-    error::TiledError,
-    layers::{Chunk, LayerData, LayerTile},
-};
+use crate::{animation::Frame, error::TiledError, Gid, MapTilesetGid};
 
-pub(crate) fn parse_animation<R: Read>(
-    parser: &mut EventReader<R>,
+// TODO: Move to animation module
+pub(crate) fn parse_animation(
+    parser: &mut impl Iterator<Item = XmlEventResult>,
 ) -> Result<Vec<Frame>, TiledError> {
     let mut animation = Vec::new();
     parse_tag!(parser, "animation", {
@@ -80,179 +71,16 @@ pub(crate) fn parse_animation<R: Read>(
     Ok(animation)
 }
 
-pub(crate) fn parse_infinite_data<R: Read>(
-    parser: &mut EventReader<R>,
-    attrs: Vec<OwnedAttribute>,
-) -> Result<LayerData, TiledError> {
-    let ((e, c), ()) = get_attrs!(
-        attrs,
-        optionals: [
-            ("encoding", encoding, |v| Some(v)),
-            ("compression", compression, |v| Some(v)),
-        ],
-        required: [],
-        TiledError::MalformedAttributes("data must have an encoding and a compression".to_string())
-    );
-
-    let mut chunks = HashMap::<(i32, i32), Chunk>::new();
-    parse_tag!(parser, "data", {
-        "chunk" => |attrs| {
-            let chunk = Chunk::new(parser, attrs, e.clone(), c.clone())?;
-            chunks.insert((chunk.x, chunk.y), chunk);
-            Ok(())
-        }
-    });
-
-    Ok(LayerData::Infinite(chunks))
-}
-
-pub(crate) fn parse_data<R: Read>(
-    parser: &mut EventReader<R>,
-    attrs: Vec<OwnedAttribute>,
-) -> Result<LayerData, TiledError> {
-    let ((e, c), ()) = get_attrs!(
-        attrs,
-        optionals: [
-            ("encoding", encoding, |v| Some(v)),
-            ("compression", compression, |v| Some(v)),
-        ],
-        required: [],
-        TiledError::MalformedAttributes("data must have an encoding and a compression".to_string())
-    );
-
-    let tiles = parse_data_line(e, c, parser)?;
-
-    Ok(LayerData::Finite(tiles))
-}
-
-pub(crate) fn parse_data_line<R: Read>(
-    encoding: Option<String>,
-    compression: Option<String>,
-    parser: &mut EventReader<R>,
-) -> Result<Vec<LayerTile>, TiledError> {
-    match (encoding, compression) {
-        (None, None) => {
-            return Err(TiledError::Other(
-                "XML format is currently not supported".to_string(),
-            ))
-        }
-        (Some(e), None) => match e.as_ref() {
-            "base64" => return parse_base64(parser).map(|v| convert_to_tiles(&v)),
-            "csv" => return decode_csv(parser),
-            e => return Err(TiledError::Other(format!("Unknown encoding format {}", e))),
-        },
-        (Some(e), Some(c)) => match (e.as_ref(), c.as_ref()) {
-            ("base64", "zlib") => {
-                return parse_base64(parser)
-                    .and_then(decode_zlib)
-                    .map(|v| convert_to_tiles(&v))
-            }
-            ("base64", "gzip") => {
-                return parse_base64(parser)
-                    .and_then(decode_gzip)
-                    .map(|v| convert_to_tiles(&v))
-            }
-            #[cfg(feature = "zstd")]
-            ("base64", "zstd") => {
-                return parse_base64(parser)
-                    .and_then(decode_zstd)
-                    .map(|v| convert_to_tiles(&v))
-            }
-            (e, c) => {
-                return Err(TiledError::Other(format!(
-                    "Unknown combination of {} encoding and {} compression",
-                    e, c
-                )))
-            }
-        },
-        _ => return Err(TiledError::Other("Missing encoding format".to_string())),
-    };
-}
-
-pub(crate) fn parse_base64<R: Read>(parser: &mut EventReader<R>) -> Result<Vec<u8>, TiledError> {
-    loop {
-        match parser.next().map_err(TiledError::XmlDecodingError)? {
-            XmlEvent::Characters(s) => {
-                return base64::decode(s.trim().as_bytes()).map_err(TiledError::Base64DecodingError)
-            }
-            XmlEvent::EndElement { name, .. } => {
-                if name.local_name == "data" {
-                    return Ok(Vec::new());
-                }
-            }
-            _ => {}
-        }
-    }
-}
-
-pub(crate) fn decode_zlib(data: Vec<u8>) -> Result<Vec<u8>, TiledError> {
-    use libflate::zlib::Decoder;
-    let mut zd =
-        Decoder::new(BufReader::new(&data[..])).map_err(|e| TiledError::DecompressingError(e))?;
-    let mut data = Vec::new();
-    match zd.read_to_end(&mut data) {
-        Ok(_v) => {}
-        Err(e) => return Err(TiledError::DecompressingError(e)),
-    }
-    Ok(data)
-}
-
-pub(crate) fn decode_gzip(data: Vec<u8>) -> Result<Vec<u8>, TiledError> {
-    use libflate::gzip::Decoder;
-    let mut zd =
-        Decoder::new(BufReader::new(&data[..])).map_err(|e| TiledError::DecompressingError(e))?;
-
-    let mut data = Vec::new();
-    zd.read_to_end(&mut data)
-        .map_err(|e| TiledError::DecompressingError(e))?;
-    Ok(data)
-}
-
-#[cfg(feature = "zstd")]
-pub(crate) fn decode_zstd(data: Vec<u8>) -> Result<Vec<u8>, TiledError> {
-    use std::io::Cursor;
-    use zstd::stream::read::Decoder;
-
-    let buff = Cursor::new(&data);
-    let mut zd = Decoder::with_buffer(buff).map_err(|e| TiledError::DecompressingError(e))?;
-
-    let mut data = Vec::new();
-    zd.read_to_end(&mut data)
-        .map_err(|e| TiledError::DecompressingError(e))?;
-    Ok(data)
-}
-
-pub(crate) fn decode_csv<R: Read>(
-    parser: &mut EventReader<R>,
-) -> Result<Vec<LayerTile>, TiledError> {
-    loop {
-        match parser.next().map_err(TiledError::XmlDecodingError)? {
-            XmlEvent::Characters(s) => {
-                let tiles = s
-                    .split(',')
-                    .map(|v| v.trim().parse().unwrap())
-                    .map(LayerTile::new)
-                    .collect();
-                return Ok(tiles);
-            }
-            XmlEvent::EndElement { name, .. } => {
-                if name.local_name == "data" {
-                    return Ok(Vec::new());
-                }
-            }
-            _ => {}
-        }
-    }
-}
-
-pub(crate) fn convert_to_tiles(all: &Vec<u8>) -> Vec<LayerTile> {
-    let mut data = Vec::new();
-    for chunk in all.chunks_exact(4) {
-        let n = chunk[0] as u32
-            + ((chunk[1] as u32) << 8)
-            + ((chunk[2] as u32) << 16)
-            + ((chunk[3] as u32) << 24);
-        data.push(LayerTile::new(n));
-    }
-    data
+pub(crate) type XmlEventResult = xml::reader::Result<xml::reader::XmlEvent>;
+
+/// Returns both the tileset and its index
+pub(crate) fn get_tileset_for_gid(
+    tilesets: &[MapTilesetGid],
+    gid: Gid,
+) -> Option<(usize, &MapTilesetGid)> {
+    tilesets
+        .iter()
+        .enumerate()
+        .rev()
+        .find(|(_idx, ts)| ts.first_gid <= gid)
 }
diff --git a/tests/lib.rs b/tests/lib.rs
index ad8839f7095501b142a87ee101979ee4fdf38d3d..396e01c5df8291d7b63452f2181f8dd24bab7652 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -1,85 +1,115 @@
-use std::path::Path;
-use std::{fs::File, path::PathBuf};
-use tiled::{Color, LayerData, Map, PropertyValue, TiledError, Tileset};
-use tiled::{LayerType, ObjectLayer, TileLayer};
+use std::path::PathBuf;
+use tiled::{
+    Color, FilesystemResourceCache, FiniteTileLayerData, Layer, LayerDataType, LayerType, Map,
+    ObjectLayer, PropertyValue, ResourceCache, TileLayer, TileLayerData,
+};
 
-fn as_tile_layer(layer: &LayerType) -> &TileLayer {
-    match layer {
+fn as_tile_layer<'map>(layer: Layer<'map>) -> TileLayer<'map> {
+    match layer.layer_type() {
         LayerType::TileLayer(x) => x,
         _ => panic!("Not a tile layer"),
     }
 }
 
-fn as_object_layer(layer: &LayerType) -> &ObjectLayer {
-    match layer {
+fn as_finite(data: &TileLayerData) -> &FiniteTileLayerData {
+    match data {
+        TileLayerData::Finite(data) => data,
+        TileLayerData::Infinite(_) => panic!("Not a finite tile layer"),
+    }
+}
+
+fn as_object_layer<'map>(layer: Layer<'map>) -> ObjectLayer<'map> {
+    match layer.layer_type() {
         LayerType::ObjectLayer(x) => x,
         _ => panic!("Not an object layer"),
     }
 }
 
-fn parse_map_without_source(p: impl AsRef<Path>) -> Result<Map, TiledError> {
-    let file = File::open(p).unwrap();
-    return Map::parse_reader(file, None);
+fn compare_everything_but_tileset_sources(r: &Map, e: &Map) {
+    assert_eq!(r.version, e.version);
+    assert_eq!(r.orientation, e.orientation);
+    assert_eq!(r.width, e.width);
+    assert_eq!(r.height, e.height);
+    assert_eq!(r.tile_width, e.tile_width);
+    assert_eq!(r.tile_height, e.tile_height);
+    assert_eq!(r.properties, e.properties);
+    assert_eq!(r.background_color, e.background_color);
+    assert_eq!(r.infinite, e.infinite);
+    r.layers()
+        .zip(e.layers())
+        .for_each(|(r, e)| assert_eq!(r.data(), e.data()));
 }
 
 #[test]
 fn test_gzip_and_zlib_encoded_and_raw_are_the_same() {
-    let z = Map::parse_file("assets/tiled_base64_zlib.tmx").unwrap();
-    let g = Map::parse_file("assets/tiled_base64_gzip.tmx").unwrap();
-    let r = Map::parse_file("assets/tiled_base64.tmx").unwrap();
-    let zstd = Map::parse_file("assets/tiled_base64_zstandard.tmx").unwrap();
-    let c = Map::parse_file("assets/tiled_csv.tmx").unwrap();
-    assert_eq!(z, g);
-    assert_eq!(z, r);
-    assert_eq!(z, c);
-    assert_eq!(z, zstd);
-
-    if let LayerData::Finite(tiles) = &as_tile_layer(&c.layers[0].layer_type).tiles {
-        assert_eq!(tiles.len(), 100 * 100);
-        assert_eq!(tiles[0].gid, 35);
-        assert_eq!(tiles[100].gid, 17);
-        assert_eq!(tiles[200].gid, 0);
-        assert_eq!(tiles[200 + 1].gid, 17);
-        assert!(tiles[9900..9999].iter().map(|t| t.gid).all(|g| g == 0));
-    } else {
-        panic!("It is wrongly recognised as an infinite map");
+    let mut cache = FilesystemResourceCache::new();
+    let z = Map::parse_file("assets/tiled_base64_zlib.tmx", &mut cache).unwrap();
+    let g = Map::parse_file("assets/tiled_base64_gzip.tmx", &mut cache).unwrap();
+    let r = Map::parse_file("assets/tiled_base64.tmx", &mut cache).unwrap();
+    let zstd = Map::parse_file("assets/tiled_base64_zstandard.tmx", &mut cache).unwrap();
+    let c = Map::parse_file("assets/tiled_csv.tmx", &mut cache).unwrap();
+    compare_everything_but_tileset_sources(&z, &g);
+    compare_everything_but_tileset_sources(&z, &r);
+    compare_everything_but_tileset_sources(&z, &c);
+    compare_everything_but_tileset_sources(&z, &zstd);
+
+    let layer = as_tile_layer(c.get_layer(0).unwrap());
+    {
+        let data = as_finite(layer.data());
+        assert_eq!(data.width(), 100);
+        assert_eq!(data.height(), 100);
     }
+
+    assert_eq!(layer.get_tile(0, 0).unwrap().id, 34);
+    assert_eq!(layer.get_tile(0, 1).unwrap().id, 16);
+    assert!(layer.get_tile(0, 2).is_none());
+    assert_eq!(layer.get_tile(1, 2).unwrap().id, 16);
+    assert!((0..99).map(|x| layer.get_tile(x, 99)).all(|t| t.is_none()));
 }
 
 #[test]
 fn test_external_tileset() {
-    let r = Map::parse_file("assets/tiled_base64.tmx").unwrap();
-    let mut e = Map::parse_file("assets/tiled_base64_external.tmx").unwrap();
-    e.tilesets[0].source = None;
-    assert_eq!(r, e);
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_base64.tmx", &mut cache).unwrap();
+    let e = Map::parse_file("assets/tiled_base64_external.tmx", &mut cache).unwrap();
+    compare_everything_but_tileset_sources(&r, &e);
 }
 
 #[test]
 fn test_sources() {
-    let e = Map::parse_file("assets/tiled_base64_external.tmx").unwrap();
+    let mut cache = FilesystemResourceCache::new();
+
+    let e = Map::parse_file("assets/tiled_base64_external.tmx", &mut cache).unwrap();
     assert_eq!(
-        e.tilesets[0].source,
-        Some(PathBuf::from("assets/tilesheet.tsx"))
+        e.tilesets()[0],
+        cache.get_tileset("assets/tilesheet.tsx").unwrap()
     );
     assert_eq!(
-        e.tilesets[0].image.as_ref().unwrap().source,
+        e.tilesets()[0].image.as_ref().unwrap().source,
         PathBuf::from("assets/tilesheet.png")
     );
 }
 
 #[test]
 fn test_just_tileset() {
-    let r = Map::parse_file("assets/tiled_base64_external.tmx").unwrap();
-    let path = "assets/tilesheet.tsx";
-    let t = Tileset::parse_with_path(File::open(path).unwrap(), 1, path).unwrap();
-    assert_eq!(r.tilesets[0], t);
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_base64_external.tmx", &mut cache).unwrap();
+    assert_eq!(
+        r.tilesets()[0],
+        cache.get_tileset("assets/tilesheet.tsx").unwrap()
+    );
 }
 
 #[test]
 fn test_infinite_tileset() {
-    let r = Map::parse_file("assets/tiled_base64_zlib_infinite.tmx").unwrap();
+    let mut cache = FilesystemResourceCache::new();
 
-    if let LayerData::Infinite(chunks) = &as_tile_layer(&r.layers[0].layer_type).tiles {
+    let r = Map::parse_file("assets/tiled_base64_zlib_infinite.tmx", &mut cache).unwrap();
+
+    if let TileLayerData::Infinite(inf) = &as_tile_layer(r.get_layer(0).unwrap()).data() {
+        let chunks = &inf.chunks;
         assert_eq!(chunks.len(), 4);
 
         assert_eq!(chunks[&(0, 0)].width, 32);
@@ -94,11 +124,13 @@ fn test_infinite_tileset() {
 
 #[test]
 fn test_image_layers() {
-    let r = Map::parse_file("assets/tiled_image_layers.tmx").unwrap();
-    assert_eq!(r.layers.len(), 2);
-    let mut image_layers = r.layers.iter().map(|x| {
-        if let LayerType::ImageLayer(img) = &x.layer_type {
-            (img, x)
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_image_layers.tmx", &mut cache).unwrap();
+    assert_eq!(r.layers().len(), 2);
+    let mut image_layers = r.layers().map(|layer| layer.data()).map(|layer| {
+        if let LayerDataType::ImageLayer(img) = &layer.layer_type {
+            (img, layer)
         } else {
             panic!("Found layer that isn't an image layer")
         }
@@ -128,9 +160,14 @@ fn test_image_layers() {
 
 #[test]
 fn test_tile_property() {
-    let r = Map::parse_file("assets/tiled_base64.tmx").unwrap();
-    let prop_value: String = if let Some(&PropertyValue::StringValue(ref v)) =
-        r.tilesets[0].tiles[0].properties.get("a tile property")
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_base64.tmx", &mut cache).unwrap();
+    let prop_value: String = if let Some(&PropertyValue::StringValue(ref v)) = r.tilesets()[0]
+        .get_tile(1)
+        .unwrap()
+        .properties
+        .get("a tile property")
     {
         v.clone()
     } else {
@@ -141,21 +178,31 @@ fn test_tile_property() {
 
 #[test]
 fn test_layer_property() {
-    let r = Map::parse_file(&Path::new("assets/tiled_base64.tmx")).unwrap();
-    let prop_value: String =
-        if let Some(&PropertyValue::StringValue(ref v)) = r.layers[0].properties.get("prop3") {
-            v.clone()
-        } else {
-            String::new()
-        };
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_base64.tmx", &mut cache).unwrap();
+    let prop_value: String = if let Some(&PropertyValue::StringValue(ref v)) =
+        r.get_layer(0).unwrap().data().properties.get("prop3")
+    {
+        v.clone()
+    } else {
+        String::new()
+    };
     assert_eq!("Line 1\r\nLine 2\r\nLine 3,\r\n  etc\r\n   ", prop_value);
 }
 
 #[test]
 fn test_object_group_property() {
-    let r = Map::parse_file("assets/tiled_object_groups.tmx").unwrap();
-    let prop_value: bool = if let Some(&PropertyValue::BoolValue(ref v)) =
-        r.layers[1].properties.get("an object group property")
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_object_groups.tmx", &mut cache).unwrap();
+    let prop_value: bool = if let Some(&PropertyValue::BoolValue(ref v)) = r
+        .layers()
+        .nth(1)
+        .unwrap()
+        .data()
+        .properties
+        .get("an object group property")
     {
         *v
     } else {
@@ -165,9 +212,11 @@ fn test_object_group_property() {
 }
 #[test]
 fn test_tileset_property() {
-    let r = Map::parse_file("assets/tiled_base64.tmx").unwrap();
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_base64.tmx", &mut cache).unwrap();
     let prop_value: String = if let Some(&PropertyValue::StringValue(ref v)) =
-        r.tilesets[0].properties.get("tileset property")
+        r.tilesets()[0].properties.get("tileset property")
     {
         v.clone()
     } else {
@@ -177,65 +226,70 @@ fn test_tileset_property() {
 }
 
 #[test]
-fn test_flipped_gid() {
-    let r = Map::parse_file("assets/tiled_flipped.tmx").unwrap();
-
-    if let LayerData::Finite(tiles) = &as_tile_layer(&r.layers[0].layer_type).tiles {
-        let t1 = tiles[0];
-        let t2 = tiles[1];
-        let t3 = tiles[2];
-        let t4 = tiles[3];
-        assert_eq!(t1.gid, t2.gid);
-        assert_eq!(t2.gid, t3.gid);
-        assert_eq!(t3.gid, t4.gid);
-        assert!(t1.flip_d);
-        assert!(t1.flip_h);
-        assert!(t1.flip_v);
-        assert!(!t2.flip_d);
-        assert!(!t2.flip_h);
-        assert!(t2.flip_v);
-        assert!(!t3.flip_d);
-        assert!(t3.flip_h);
-        assert!(!t3.flip_v);
-        assert!(t4.flip_d);
-        assert!(!t4.flip_h);
-        assert!(!t4.flip_v);
-    } else {
-        assert!(false, "It is wrongly recognised as an infinite map");
-    }
+fn test_flipped() {
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_flipped.tmx", &mut cache).unwrap();
+    let layer = as_tile_layer(r.get_layer(0).unwrap());
+
+    let t1 = layer.get_tile(0, 0).unwrap();
+    let t2 = layer.get_tile(1, 0).unwrap();
+    let t3 = layer.get_tile(0, 1).unwrap();
+    let t4 = layer.get_tile(1, 1).unwrap();
+    assert_eq!(t1.id, t2.id);
+    assert_eq!(t2.id, t3.id);
+    assert_eq!(t3.id, t4.id);
+    assert!(t1.flip_d);
+    assert!(t1.flip_h);
+    assert!(t1.flip_v);
+    assert!(!t2.flip_d);
+    assert!(!t2.flip_h);
+    assert!(t2.flip_v);
+    assert!(!t3.flip_d);
+    assert!(t3.flip_h);
+    assert!(!t3.flip_v);
+    assert!(t4.flip_d);
+    assert!(!t4.flip_h);
+    assert!(!t4.flip_v);
 }
 
 #[test]
 fn test_ldk_export() {
-    let r = Map::parse_file("assets/ldk_tiled_export.tmx").unwrap();
-    if let LayerData::Finite(tiles) = &as_tile_layer(&r.layers[0].layer_type).tiles {
-        assert_eq!(tiles.len(), 8 * 8);
-        assert_eq!(tiles[0].gid, 0);
-        assert_eq!(tiles[8].gid, 1);
-    } else {
-        assert!(false, "It is wrongly recognised as an infinite map");
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/ldk_tiled_export.tmx", &mut cache).unwrap();
+    let layer = as_tile_layer(r.get_layer(0).unwrap());
+    {
+        let data = as_finite(layer.data());
+        assert_eq!(data.width(), 8);
+        assert_eq!(data.height(), 8);
     }
+    assert!(layer.get_tile(0, 0).is_none());
+    assert_eq!(layer.get_tile(0, 1).unwrap().id, 0);
 }
 
 #[test]
 fn test_parallax_layers() {
-    let r = Map::parse_file("assets/tiled_parallax.tmx").unwrap();
-    for (i, layer) in r.layers.iter().enumerate() {
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_parallax.tmx", &mut cache).unwrap();
+    for (i, layer) in r.layers().enumerate() {
+        let data = layer.data();
         match i {
             0 => {
-                assert_eq!(layer.name, "Background");
-                assert_eq!(layer.parallax_x, 0.5);
-                assert_eq!(layer.parallax_y, 0.75);
+                assert_eq!(data.name, "Background");
+                assert_eq!(data.parallax_x, 0.5);
+                assert_eq!(data.parallax_y, 0.75);
             }
             1 => {
-                assert_eq!(layer.name, "Middle");
-                assert_eq!(layer.parallax_x, 1.0);
-                assert_eq!(layer.parallax_y, 1.0);
+                assert_eq!(data.name, "Middle");
+                assert_eq!(data.parallax_x, 1.0);
+                assert_eq!(data.parallax_y, 1.0);
             }
             2 => {
-                assert_eq!(layer.name, "Foreground");
-                assert_eq!(layer.parallax_x, 2.0);
-                assert_eq!(layer.parallax_y, 2.0);
+                assert_eq!(data.name, "Foreground");
+                assert_eq!(data.parallax_x, 2.0);
+                assert_eq!(data.parallax_y, 2.0);
             }
             _ => panic!("unexpected layer"),
         }
@@ -244,9 +298,12 @@ fn test_parallax_layers() {
 
 #[test]
 fn test_object_property() {
-    let r = parse_map_without_source(&Path::new("assets/tiled_object_property.tmx")).unwrap();
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_object_property.tmx", &mut cache).unwrap();
+    let layer = r.get_layer(1).unwrap();
     let prop_value = if let Some(PropertyValue::ObjectValue(v)) =
-        as_object_layer(&r.layers[1].layer_type).objects[0]
+        as_object_layer(layer).data().objects[0]
             .properties
             .get("object property")
     {
@@ -259,9 +316,11 @@ fn test_object_property() {
 
 #[test]
 fn test_tint_color() {
-    let r = Map::parse_file("assets/tiled_image_layers.tmx").unwrap();
+    let mut cache = FilesystemResourceCache::new();
+
+    let r = Map::parse_file("assets/tiled_image_layers.tmx", &mut cache).unwrap();
     assert_eq!(
-        r.layers[0].tint_color,
+        r.get_layer(0).unwrap().data().tint_color,
         Some(Color {
             alpha: 0x12,
             red: 0x34,
@@ -270,7 +329,7 @@ fn test_tint_color() {
         })
     );
     assert_eq!(
-        r.layers[1].tint_color,
+        r.get_layer(1).unwrap().data().tint_color,
         Some(Color {
             alpha: 0xFF,
             red: 0x12,