diff --git a/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/App.java b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/App.java
index b6813c2fc487069a71bc60d7ce5c4bec0f13e7ba..dc4206406ee5027dabfc27e5a246d4107a255c6e 100644
--- a/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/App.java
+++ b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/App.java
@@ -1,5 +1,9 @@
 package tdt4140.gr1800.app.core;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -17,7 +21,7 @@ public class App {
 	private Collection<GeoLocations> geoLocations;
 
 	public Iterable<String> getGeoLocationNames() {
-		Collection<String> names = new ArrayList<String>(geoLocations.size());
+		Collection<String> names = new ArrayList<String>(geoLocations != null ? geoLocations.size() : 0);
 		if (geoLocations != null) {
 			for (GeoLocations geoLocations : geoLocations) {
 				names.add(geoLocations.getName());
@@ -25,7 +29,7 @@ public class App {
 		}
 		return names;
 	}
-	
+
 	public boolean hasGeoLocations(String name) {
 		if (geoLocations != null) {
 			for (GeoLocations geoLocations : geoLocations) {
@@ -47,4 +51,46 @@ public class App {
 		}
 		return null;
 	}
+	
+	// 
+
+	private DocumentStorageImpl<Collection<GeoLocations>, File> documentStorage = new DocumentStorageImpl<Collection<GeoLocations>, File>() {
+
+		@Override
+		protected Collection<GeoLocations> getDocument() {
+			return geoLocations;
+		}
+
+		@Override
+		protected void setDocument(Collection<GeoLocations> document) {
+			geoLocations = document;
+		}
+
+		@Override
+		protected Collection<GeoLocations> createDocument() {
+			return new ArrayList<GeoLocations>();
+		}
+
+		@Override
+		protected Collection<GeoLocations> loadDocument(File file) throws IOException {
+			try {
+				return geoLocationsLoader.loadLocations(new FileInputStream(file));
+			} catch (Exception e) {
+				throw new IOException(e);
+			}
+		}
+
+		@Override
+		protected void storeDocument(Collection<GeoLocations> document, File file) throws IOException {
+			try {
+				geoLocationsLoader.saveLocations(document, new FileOutputStream(file));
+			} catch (Exception e) {
+				throw new IOException(e);
+			}
+		}
+	};
+
+	public IDocumentStorage<File> getDocumentStorage() {
+		return documentStorage;
+	}
 }
diff --git a/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/DocumentStorageImpl.java b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/DocumentStorageImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..9314f27249bf693cb90bcbf07e2c9a2c0a851e60
--- /dev/null
+++ b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/DocumentStorageImpl.java
@@ -0,0 +1,49 @@
+package tdt4140.gr1800.app.core;
+
+import java.io.IOException;
+
+public abstract class DocumentStorageImpl<D, L> implements IDocumentStorage<L> {
+
+	private L documentLocation;
+
+	@Override
+	public L getDocumentLocation() {
+		return documentLocation;
+	}
+
+	@Override
+	public void setDocumentLocation(L documentLocation) {
+		this.documentLocation = documentLocation;
+	}
+
+	protected abstract D getDocument();
+	protected abstract void setDocument(D document);
+
+	protected abstract D createDocument();
+	protected abstract D loadDocument(L storage) throws IOException;
+	protected abstract void storeDocument(D document, L storage) throws IOException;
+	
+	@Override
+	public void newDocument() {
+		setDocument(createDocument());
+	}
+
+	@Override
+	public void openDocument(L storage) throws IOException {
+		setDocument(loadDocument(storage));
+	}
+
+	@Override
+	public void saveDocument() throws IOException {
+		storeDocument(getDocument(), getDocumentLocation());
+	}
+
+	public void saveDocumentAs(L documentLocation) throws IOException {
+		setDocumentLocation(documentLocation);
+		saveDocument();
+	}
+	
+	public void saveCopyAs(L documentLocation) throws IOException {
+		storeDocument(getDocument(), documentLocation);
+	}
+}
diff --git a/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/IDocumentStorage.java b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/IDocumentStorage.java
new file mode 100644
index 0000000000000000000000000000000000000000..ceef9616362ec82ffd15246c7811942b39ddc35e
--- /dev/null
+++ b/tdt4140-gr1800/app.core/src/main/java/tdt4140/gr1800/app/core/IDocumentStorage.java
@@ -0,0 +1,12 @@
+package tdt4140.gr1800.app.core;
+
+import java.io.IOException;
+
+public interface IDocumentStorage<L> {
+	public L getDocumentLocation();
+	public void setDocumentLocation(L documentLocation);
+
+	public void newDocument();
+	public void openDocument(L documentLocation) throws IOException;
+	public void saveDocument() throws IOException;
+}
diff --git a/tdt4140-gr1800/app.ui/src/main/java/tdt4140/gr1800/app/ui/FileMenuController.java b/tdt4140-gr1800/app.ui/src/main/java/tdt4140/gr1800/app/ui/FileMenuController.java
new file mode 100644
index 0000000000000000000000000000000000000000..f63754c3c4ab28db23b288e2e0711a50f0e504a5
--- /dev/null
+++ b/tdt4140-gr1800/app.ui/src/main/java/tdt4140/gr1800/app/ui/FileMenuController.java
@@ -0,0 +1,129 @@
+package tdt4140.gr1800.app.ui;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.scene.control.Menu;
+import javafx.scene.control.MenuItem;
+import javafx.stage.FileChooser;
+import tdt4140.gr1800.app.core.IDocumentStorage;
+
+public class FileMenuController {
+
+	private IDocumentStorage<File> documentStorage;
+
+	public void setDocumentStorage(IDocumentStorage<File> documentStorage) {
+		this.documentStorage = documentStorage;
+	}
+
+	private Consumer<IDocumentStorage<File>> onDocumentChanged;
+	
+	public void setOnDocumentChanged(Consumer<IDocumentStorage<File>> onDocumentChanged) {
+		this.onDocumentChanged = onDocumentChanged;
+	}
+	
+	@FXML
+	public void handleNewAction() {
+		documentStorage.newDocument();
+		fireDocumentChanged();
+	}
+
+	private void fireDocumentChanged() {
+		if (onDocumentChanged != null) {
+			onDocumentChanged.accept(documentStorage);
+		}
+	}
+
+	private List<File> recentFiles = new ArrayList<File>();
+
+	@FXML
+	private Menu recentMenu;
+	
+	protected void updateRecentMenu(File file) {
+		recentFiles.remove(file);
+		recentFiles.add(0, file);
+		recentMenu.getItems().clear();
+		for (File recentFile : recentFiles) {
+			MenuItem menuItem = new MenuItem();
+			menuItem.setText(recentFile.toString());
+			menuItem.setOnAction(event -> handleOpenAction(event));
+			recentMenu.getItems().add(menuItem);
+		}
+	}
+	
+	private FileChooser fileChooser;
+
+	protected FileChooser getFileChooser() {
+		if (fileChooser == null) {
+			fileChooser = new FileChooser();
+		}
+		return fileChooser;
+	}
+
+	@FXML
+	public void handleOpenAction(ActionEvent event) {
+		File selection = null;
+		if (event.getSource() instanceof MenuItem) {
+			File file = new File(((MenuItem) event.getSource()).getText());
+			if (file.exists()) {
+				selection = file;
+			}
+		}
+		if (selection == null) {
+			FileChooser fileChooser = getFileChooser();
+			selection = fileChooser.showOpenDialog(null);
+		}
+		if (selection != null) {
+			try {
+				documentStorage.openDocument(selection);
+				updateRecentMenu(selection);
+				fireDocumentChanged();
+			} catch (IOException e) {
+				// TODO
+			}
+		}
+	}
+	
+	@FXML
+	public void handleSaveAction() {
+		try {
+			documentStorage.saveDocument();
+		} catch (IOException e) {
+			// TODO
+		}
+	}
+	
+	@FXML
+	public void handleSaveAsAction() {
+		FileChooser fileChooser = getFileChooser();
+		File selection = fileChooser.showSaveDialog(null);
+		File oldStorage = documentStorage.getDocumentLocation();
+		try {
+			documentStorage.setDocumentLocation(selection);
+			documentStorage.saveDocument();
+		} catch (IOException e) {
+			// TODO
+			documentStorage.setDocumentLocation(oldStorage);
+		}
+	}
+	
+	@FXML
+	public void handleSaveCopyAsAction() {
+		FileChooser fileChooser = getFileChooser();
+		File selection = fileChooser.showSaveDialog(null);
+		File oldStorage = documentStorage.getDocumentLocation();
+		try {
+			documentStorage.setDocumentLocation(selection);
+			documentStorage.saveDocument();
+		} catch (IOException e) {
+			// TODO
+		} finally {
+			documentStorage.setDocumentLocation(oldStorage);
+		}
+	}
+}
diff --git a/tdt4140-gr1800/app.ui/src/main/java/tdt4140/gr1800/app/ui/FxAppController.java b/tdt4140-gr1800/app.ui/src/main/java/tdt4140/gr1800/app/ui/FxAppController.java
index 753b0caf45c168a803261e534e5a08c26e547fa8..22dc763b0309af78a8b853be3261a04aafdfa2de 100644
--- a/tdt4140-gr1800/app.ui/src/main/java/tdt4140/gr1800/app/ui/FxAppController.java
+++ b/tdt4140-gr1800/app.ui/src/main/java/tdt4140/gr1800/app/ui/FxAppController.java
@@ -1,48 +1,30 @@
 package tdt4140.gr1800.app.ui;
 
-import java.io.File;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.lynden.gmapsfx.GoogleMapView;
-import com.lynden.gmapsfx.MapComponentInitializedListener;
-import com.lynden.gmapsfx.javascript.event.UIEventType;
-import com.lynden.gmapsfx.javascript.object.GoogleMap;
-import com.lynden.gmapsfx.javascript.object.MVCArray;
-import com.lynden.gmapsfx.javascript.object.MapOptions;
-import com.lynden.gmapsfx.javascript.object.MapShape;
-import com.lynden.gmapsfx.javascript.object.MapTypeIdEnum;
-import com.lynden.gmapsfx.javascript.object.Marker;
-import com.lynden.gmapsfx.javascript.object.MarkerOptions;
-import com.lynden.gmapsfx.shapes.Polyline;
-import com.lynden.gmapsfx.shapes.PolylineOptions;
+import java.util.Iterator;
 
+import fxmapcontrol.MapBase;
+import fxmapcontrol.MapItemsControl;
+import fxmapcontrol.MapNode;
+import fxmapcontrol.MapTileLayer;
 import javafx.fxml.FXML;
-import javafx.scene.control.Alert;
-import javafx.scene.control.Alert.AlertType;
 import javafx.scene.control.ComboBox;
 import javafx.scene.control.Slider;
-import javafx.scene.control.TextField;
-import javafx.stage.DirectoryChooser;
-import javafx.stage.FileChooser;
 import tdt4140.gr1800.app.core.App;
 import tdt4140.gr1800.app.core.GeoLocations;
 import tdt4140.gr1800.app.core.LatLong;
 
-public class FxAppController implements MapComponentInitializedListener {
+public class FxAppController {
 
 	@FXML
-	private TextField geolocationsFileText;
+	private FileMenuController fileMenuController;
 
 	@FXML
 	private ComboBox<String> geoLocationsSelector;
 
 	@FXML
-	private GoogleMapView mapView;
-	private GoogleMap map;
+	private MapBase mapView;
+	
+	private MapItemsControl<MapNode> markersParent;
 	
 	@FXML
 	private Slider zoomSlider;
@@ -51,133 +33,62 @@ public class FxAppController implements MapComponentInitializedListener {
 
 	@FXML
 	public void initialize() {
-		mapView.addMapInializedListener(this);
 		app = new App();
+		fileMenuController.setDocumentStorage(app.getDocumentStorage());
+		fileMenuController.setOnDocumentChanged(documentStorage -> initMapMarkers());
 		geoLocationsSelector.getSelectionModel().selectedItemProperty().addListener((stringProperty, oldValue, newValue) -> updateGeoLocations());
-		zoomSlider.valueProperty().addListener((doubleProperty, oldValue, newValue) -> map.setZoom((int) zoomSlider.getValue()));
+
+		mapView.getChildren().add(MapTileLayer.getOpenStreetMapLayer());
+		mapView.zoomLevelProperty().bind(zoomSlider.valueProperty());
+		markersParent = new MapItemsControl<MapNode>();
+		mapView.getChildren().add(markersParent);
 	}
 
 	private Object updateGeoLocations() {
 		return null;
 	}
 
-	private Map<GeoLocations, MapShape> shapes = new HashMap<GeoLocations, MapShape>();
-	private Map<GeoLocations, Collection<Marker>> markers = new HashMap<GeoLocations, Collection<Marker>>();
-	
 	private void initMapMarkers() {
-		map.clearMarkers();
+		markersParent.getItems().clear();
 		for (String geoLocationName : app.getGeoLocationNames()) {
 			GeoLocations geoLocations = app.getGeoLocations(geoLocationName);
-			if (geoLocations.isPath()) {
-				Collection<com.lynden.gmapsfx.javascript.object.LatLong> latLongs = new ArrayList<com.lynden.gmapsfx.javascript.object.LatLong>();
-				for (LatLong latLong : geoLocations) {
-					double lat = latLong.latitude, lon = latLong.longitude;
-					latLongs.add(new com.lynden.gmapsfx.javascript.object.LatLong(lat, lon));
-				}
-				MVCArray mvc = new MVCArray(latLongs.toArray(new com.lynden.gmapsfx.javascript.object.LatLong[latLongs.size()]));
-				PolylineOptions options = new PolylineOptions()
-					.path(mvc)
-					.strokeColor("red")
-					.strokeWeight(2);
-				Polyline polyline = new Polyline(options);
-				map.addMapShape(polyline);
-				this.shapes.put(geoLocations, polyline);
-			} else {
-				Collection<Marker> markers = new ArrayList<Marker>();
-				for (LatLong latLong : geoLocations) {
-					double lat = latLong.latitude, lon = latLong.longitude;
-					MarkerOptions options = new MarkerOptions().label(geoLocationName)
-							.position(new com.lynden.gmapsfx.javascript.object.LatLong(lat, lon));
-					Marker marker = new Marker(options);
-					map.addMarker(marker);
-					markers.add(marker);
+			MapMarker lastMarker = null;
+			for (LatLong latLong : geoLocations) {
+				MapMarker mapMarker = new MapMarker(latLong);
+				markersParent.getItems().add(mapMarker);
+				if (geoLocations.isPath() && lastMarker != null) {
+					MapPathLine pathLine = new MapPathLine(lastMarker, mapMarker);
+					markersParent.getItems().add(pathLine);
 				}
-				this.markers.put(geoLocations, markers);
+				lastMarker = mapMarker;
 			}
 			geoLocationsSelector.getItems().add(geoLocationName);
 		}
-		map.setCenter(getCenter(null));
-		map.addMouseEventHandler(UIEventType.click, (event) -> {
-		   com.lynden.gmapsfx.javascript.object.LatLong latLong = event.getLatLong();
-		   System.out.println("Latitude: " + latLong.getLatitude());
-		   System.out.println("Longitude: " + latLong.getLongitude());
-		});
+		LatLong center = getCenter(null);
 		System.out.println("Map markers initialized");
 	}
 
-	private com.lynden.gmapsfx.javascript.object.LatLong getCenter(GeoLocations geoLocations) {
+	private LatLong getCenter(GeoLocations geoLocations) {
 		double latSum = 0.0, lonSum = 0.0;
 		int num = 0;
+		Iterator<String> names = null;
 		if (geoLocations == null) {
-			for (String geoLocationName : app.getGeoLocationNames()) {
-				geoLocations = app.getGeoLocations(geoLocationName);
-				for (LatLong latLong : geoLocations) {
-					double lat = latLong.latitude, lon = latLong.longitude;
-					latSum += lat;
-					lonSum += lon;
-					num++;
-				}
+			names = app.getGeoLocationNames().iterator();
+		}
+		while (geoLocations != null || (names != null && names.hasNext())) {
+			if (names != null) {
+				geoLocations = app.getGeoLocations(names.next());
 			}
-		} else {
 			for (LatLong latLong : geoLocations) {
 				double lat = latLong.latitude, lon = latLong.longitude;
 				latSum += lat;
 				lonSum += lon;
 				num++;
 			}
-		}
-		return new com.lynden.gmapsfx.javascript.object.LatLong(latSum / num, lonSum / num);
-	}
-	
-	@Override
-	public void mapInitialized() {
-		// Set the initial properties of the map.
-		MapOptions mapOptions = new MapOptions();
-
-		mapOptions
-			.mapType(MapTypeIdEnum.ROADMAP)
-			.center(new com.lynden.gmapsfx.javascript.object.LatLong(63.0, 10.0))
-			.overviewMapControl(false)
-			.panControl(false)
-			.rotateControl(false)
-			.scaleControl(false)
-			.streetViewControl(false)
-			.zoomControl(false)
-			.zoom(zoomSlider.getValue());
-		map = mapView.createMap(mapOptions);
-		System.out.println("Map initialized: " + map);
-		if (app.getGeoLocationNames().iterator().hasNext()) {
-			initMapMarkers();
-		}
-	}
-	
-	@FXML
-	public void loadGeolocationsFile() {
-		try {
-			URI fileUri = new URI(geolocationsFileText.getText());
-			app.loadGeoLocations(fileUri);
-			System.out.println("GeoLocations initialized: " + app.getGeoLocationNames());
-			if (map != null) {
-				initMapMarkers();
+			if (names != null) {
+				geoLocations = null;
 			}
-		} catch (Exception e) {
-			Alert alert = new Alert(AlertType.ERROR);
-			alert.setTitle("Exception when loading from " + geolocationsFileText.getText() + ": " + e.getMessage());
-			alert.setContentText(e.getMessage());
-			alert.show();
-		}
-	}
-
-	@FXML
-	public void handleBrowseGeolocationsFile() {
-		FileChooser fileChooser = new FileChooser();
-		fileChooser.setTitle("Select geo-locations file");
-
-		File file = fileChooser.showOpenDialog(mapView.getScene().getWindow());
-		if (file == null) {
-			file = new File(".");
 		}
-		geolocationsFileText.setText(file.toURI().toString());
-		geolocationsFileText.getOnAction().handle(null);
+		return new LatLong(latSum / num, lonSum / num);
 	}
 }
diff --git a/tdt4140-gr1800/app.ui/src/main/resources/tdt4140/gr1800/app/ui/FileMenu.fxml b/tdt4140-gr1800/app.ui/src/main/resources/tdt4140/gr1800/app/ui/FileMenu.fxml
new file mode 100644
index 0000000000000000000000000000000000000000..9b1209b8feb26c402e0ffb0e15d9a69a63cc97bd
--- /dev/null
+++ b/tdt4140-gr1800/app.ui/src/main/resources/tdt4140/gr1800/app/ui/FileMenu.fxml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import java.lang.*?>
+<?import javafx.scene.control.Menu?>
+<?import javafx.scene.control.MenuItem?>
+
+<Menu xmlns:fx="http://javafx.com/fxml" text="File" fx:controller="tdt4140.gr1800.app.ui.FileMenuController">
+	<items>
+		<MenuItem text="New" onAction="#handleNewAction"/>
+		<MenuItem text="Open..." onAction="#handleOpenAction"/>
+		<Menu fx:id="recentMenu" text="Open Recent"/>
+		<MenuItem text="Save" onAction="#handleSaveAction"/>
+		<MenuItem text="Save As..." onAction="#handleSaveAsAction"/>
+		<MenuItem text="Save Copy As..." onAction="#handleSaveCopyAsAction"/>
+	</items>
+</Menu>