From aa293da1f8f58994bd3320b5bafe02731c23603d Mon Sep 17 00:00:00 2001
From: jshjelse <jshjelse@stud.ntnu.no>
Date: Sun, 31 Dec 2023 10:44:13 +0100
Subject: [PATCH] Ferdigstille

---
 css/style.css                         |   5 +
 index.html                            |  62 +++++++-
 javascript/exampleData/points.geojson |  35 +++++
 javascript/heatmap.js                 |  28 ++++
 javascript/leaflet-heat.js            |  11 ++
 javascript/mapChange.js               |  67 ++------
 javascript/pointMap.js                | 218 ++++++++++++++++++++++++++
 javascript/tin.js                     |  23 +--
 javascript/voronoi.js                 |  57 +++++--
 9 files changed, 430 insertions(+), 76 deletions(-)
 create mode 100644 javascript/exampleData/points.geojson
 create mode 100644 javascript/heatmap.js
 create mode 100644 javascript/leaflet-heat.js
 create mode 100644 javascript/pointMap.js

diff --git a/css/style.css b/css/style.css
index 95ae4c6..ac526df 100644
--- a/css/style.css
+++ b/css/style.css
@@ -46,6 +46,11 @@ p {
     flex-grow: 2;
     flex-direction: row;
 }
+.box4 {
+    display: flex;
+    flex-grow: 4;
+    flex-direction: row;
+}
 
 .button {
     font-family: monospace;
diff --git a/index.html b/index.html
index 64f15a1..f34b0a0 100644
--- a/index.html
+++ b/index.html
@@ -38,9 +38,10 @@
                     <button class="button" onclick="openBox('unionBox')">Union</button>
                 </div>
                 <div id="buttons2" class="box2" style="display: none;">
-                    <p style="width: 32vw;"></p>
+                    <p style="width: 25vw;"></p>
                     <button class="button" onclick="TIN()">TIN</button>
                     <button class="button" onclick="voronoi()">Voronoi</button>
+                    <button class="button" onclick="heatmap()">Heatmap</button>
                 </div>
             </div>
             
@@ -270,6 +271,47 @@
                         
                         <p id="tutorial" class="tutorial"></p>
                     </div>
+
+                    <div id="loadPointsBox" class="box">
+                        <div class="box3">
+                            <h1 style="font-size: 4vh; text-align: center; width: 36vw;">Load points</h1>
+                            
+                            <svg style="cursor: pointer; padding-top: 15px;" onclick="closeBox('loadPointsBox')"
+                            xmlns="http://www.w3.org/2000/svg" width="3vw" height="3vw" fill="currentColor" class="bi bi-x-circle-fill" viewBox="0 0 16 16">
+                                <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
+                            </svg>
+                        </div>
+                        
+                        <button id="examplePoints" class="onClick" style="line-height: 10vh;" onclick="handleDefaultPoints()">Load example points</button>
+
+                        <input id="fileInput2" type="file" accept=".geojson" style="display: none;">
+                        <button id="loadPoints" class="onClick" style="margin-top: 2vh; line-height: 10vh;" onclick="loadPoints()">Load points from file</button>
+
+                        <p id="savePoints" style="display: none; margin-top: 2vh;">Save points to file</p><br>
+                        <input id="fileName" style="display: none;"><br>
+                        <button id="save" class="onClick" style="display: none; margin-top: 2vh;" onclick="saveToFile()">Save</button>
+
+                        <button id="removePoints" class="onClick" style="margin-top: 2vh; line-height: 10vh; display: none;" onclick="handleDefaultPoints()">Remove current points</button>
+                    </div>
+
+                    <div id="makeNewPointBox" class="box">
+                        <div class="box3">
+                            <h1 style="font-size: 4vh; text-align: center; width: 36vw;">Make new point</h1>
+
+                            <svg style="cursor: pointer; padding-top: 15px;" onclick="closeBox('makeNewPointBox')"
+                            xmlns="http://www.w3.org/2000/svg" width="3vw" height="3vw" fill="currentColor" class="bi bi-x-circle-fill" viewBox="0 0 16 16">
+                                <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
+                            </svg>
+                        </div>
+
+                        <p>Lengdegrader: <input id="lengdegrader" readonly></p> <!-- 10.xxx -->
+                        <p>Breddegrader: <input id="breddegrader" readonly></p> <!-- 6x.xxx -->
+
+                        <p>Set category: <input id="pointCategory"></p>
+                        <p>Set name: <input id="pointName"></p>
+
+                        <button id="savePoint" class="onClick" style="margin-top: 2vh;" onclick="savePoint()">Save point</button>
+                    </div>
                 </div>
 
                 <div class="box2">
@@ -279,11 +321,21 @@
                             <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.5 4.5a.5.5 0 0 0-1 0v3h-3a.5.5 0 0 0 0 1h3v3a.5.5 0 0 0 1 0v-3h3a.5.5 0 0 0 0-1h-3v-3z"/>
                         </svg>
 
+                        <svg id="pointOpener" style="cursor: pointer; border: none; position: absolute; color: orangered; z-index: 999; display: none;" onclick="openBox('loadPointsBox');"
+                        xmlns="http://www.w3.org/2000/svg" width="6vh" height="6vh" fill="currentColor" class="bi bi-plus-circle-fill" viewBox="0 0 16 16">
+                            <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.5 4.5a.5.5 0 0 0-1 0v3h-3a.5.5 0 0 0 0 1h3v3a.5.5 0 0 0 1 0v-3h3a.5.5 0 0 0 0-1h-3v-3z"/>
+                        </svg>
+
                         <svg id="mapChanger" style="margin-left: 8vh; cursor: pointer; border: none; position: absolute; color: orangered; z-index: 999;" onclick="changeMap()"
                         xmlns="http://www.w3.org/2000/svg" width="6vh" height="6vh" fill="currentColor" class="bi bi-pin-map-fill" viewBox="0 0 16 16">
                             <path fill-rule="evenodd" d="M3.1 11.2a.5.5 0 0 1 .4-.2H6a.5.5 0 0 1 0 1H3.75L1.5 15h13l-2.25-3H10a.5.5 0 0 1 0-1h2.5a.5.5 0 0 1 .4.2l3 4a.5.5 0 0 1-.4.8H.5a.5.5 0 0 1-.4-.8l3-4z"/>
                             <path fill-rule="evenodd" d="M4 4a4 4 0 1 1 4.5 3.969V13.5a.5.5 0 0 1-1 0V7.97A4 4 0 0 1 4 3.999z"/>
                         </svg>
+
+                        <svg id="addPointButton" style="margin-left: 16vh; cursor: pointer; border: none; position: absolute; color: orangered; z-index: 999; display: none;" onclick="addPoint(), event.stopPropagation()"
+                        xmlns="http://www.w3.org/2000/svg" width="6vh" height="6vh" fill="currentColor" class="bi bi-geo" viewBox="0 0 16 16">
+                            <path fill-rule="evenodd" d="M8 1a3 3 0 1 0 0 6 3 3 0 0 0 0-6M4 4a4 4 0 1 1 4.5 3.969V13.5a.5.5 0 0 1-1 0V7.97A4 4 0 0 1 4 3.999zm2.493 8.574a.5.5 0 0 1-.411.575c-.712.118-1.28.295-1.655.493a1.319 1.319 0 0 0-.37.265.301.301 0 0 0-.057.09V14l.002.008a.147.147 0 0 0 .016.033.617.617 0 0 0 .145.15c.165.13.435.27.813.395.751.25 1.82.414 3.024.414s2.273-.163 3.024-.414c.378-.126.648-.265.813-.395a.619.619 0 0 0 .146-.15.148.148 0 0 0 .015-.033L12 14v-.004a.301.301 0 0 0-.057-.09 1.318 1.318 0 0 0-.37-.264c-.376-.198-.943-.375-1.655-.493a.5.5 0 1 1 .164-.986c.77.127 1.452.328 1.957.594C12.5 13 13 13.4 13 14c0 .426-.26.752-.544.977-.29.228-.68.413-1.116.558-.878.293-2.059.465-3.34.465-1.281 0-2.462-.172-3.34-.465-.436-.145-.826-.33-1.116-.558C3.26 14.752 3 14.426 3 14c0-.599.5-1 .961-1.243.505-.266 1.187-.467 1.957-.594a.5.5 0 0 1 .575.411"/>
+                        </svg>
                     </div>
 
                     <div style="width: 95vw;"></div>
@@ -301,6 +353,12 @@
         <!-- Leaflet js -->
         <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
 
+        <!-- Saving files -->
+        <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"></script>
+
+        <!-- Heatmap -->
+        <script src="javascript/leaflet-heat.js"></script>
+
         <!-- My js -->
 
         <!-- General -->
@@ -327,8 +385,10 @@
         <!-- Map change -->
 
         <script src="javascript/mapChange.js"></script>
+        <script src="javascript/pointMap.js"></script>
         <script src="javascript/voronoi.js"></script>
         <script src="javascript/tin.js"></script>
+        <script src="javascript/heatmap.js"></script>
     
     </body>
 </html>
diff --git a/javascript/exampleData/points.geojson b/javascript/exampleData/points.geojson
new file mode 100644
index 0000000..799c45c
--- /dev/null
+++ b/javascript/exampleData/points.geojson
@@ -0,0 +1,35 @@
+{
+"type": "FeatureCollection",
+"features": [
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4044907, 63.4173049]}, "properties": {"category": "Universitet", "name": "NTNU Gløshaugen"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4696324, 63.4079572]}, "properties": {"category": "Universitet", "name": "NTNU Dragvoll"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.388918, 63.4203714]}, "properties": {"category": "Universitet", "name": "NTNU Øya"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4347967, 63.4235678]}, "properties": {"category": "Universitet", "name": "NTNU Tyholt"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4143153, 63.433756]}, "properties": {"category": "Universitet", "name": "NTNU Solsiden"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4336351, 63.4138729]}, "properties": {"category": "Universitet", "name": "NTNU Moholt"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4032871, 63.4340888]}, "properties": {"category": "Universitet", "name": "NTNU Olavskvartalet"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4071882, 63.4107064]}, "properties": {"category": "Universitet", "name": "NTNU Lerkendal og Valgrinda"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3861107, 63.4288662]}, "properties": {"category": "Universitet", "name": "NTNU Kalvskinnet"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4540302, 63.4473022]}, "properties": {"category": "Universitet", "name": "NTNU Ringve"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3483128, 63.4415347]}, "properties": {"category": "Universitet", "name": "NTNU Heggdalen"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4492127, 63.4521411]}, "properties": {"category": "Universitet", "name": "NTNU Østmarka"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3981586, 63.4373727]}, "properties": {"category": "Universitet", "name": "NTNU Brattørkaia"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3992594, 63.4152944]}, "properties": {"category": "Universitet", "name": "NTNU Elgeseter"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4837531, 63.4393659]}, "properties": {"category": "Universitet", "name": "NTNU Rotvoll"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4660445, 63.42338]}, "properties": {"category": "Universitet", "name": "NTNU Tunga"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4176387, 63.4141732]}, "properties": {"category": "Studentby", "name": "Berg Studentby"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4301549, 63.4110984]}, "properties": {"category": "Studentby", "name": "Moholt Studentby"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4372857, 63.3989126]}, "properties": {"category": "Studentby", "name": "Steinan Studentby"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3918823, 63.420511]}, "properties": {"category": "Studentby", "name": "Bloksberg Studentby"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4382451, 63.4116778]}, "properties": {"category": "Studentby", "name": "Karinelund Studentby"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4945864, 63.4211031]}, "properties": {"category": "Studentby", "name": "Jakobsliveien 55"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.396918, 63.4226982]}, "properties": {"category": "Studentby", "name": "Klostergata 18"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3963873, 63.4227505]}, "properties": {"category": "Studentby", "name": "Klostergata 20"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3891634, 63.4232409]}, "properties": {"category": "Studentby", "name": "Klostergata 56"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4126211, 63.4147073]}, "properties": {"category": "Studentby", "name": "Nedre Berg Studentby"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3973319, 63.418334]}, "properties": {"category": "Studentby", "name": "Magnus den godes gate 2"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4037983, 63.4215247]}, "properties": {"category": "Studentby", "name": "Nedre Singsakerslette"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3890993, 63.4276236]}, "properties": {"category": "Studentby", "name": "Sverresgate 8"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4122453, 63.4337663]}, "properties": {"category": "Studentby", "name": "Nedre elvehavn Studentby"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3982518, 63.4163341]}, "properties": {"category": "Studentby", "name": "Teknobyen Studentboliger"}},
+{"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4003657, 63.4117822]}, "properties": {"category": "Studentby", "name": "Lerkendal Studentby"}}]}
\ No newline at end of file
diff --git a/javascript/heatmap.js b/javascript/heatmap.js
new file mode 100644
index 0000000..4fec200
--- /dev/null
+++ b/javascript/heatmap.js
@@ -0,0 +1,28 @@
+var isHeat = false;
+
+var intensity = 100;
+var heat = null;
+
+function heatmap() {
+    if (points != null) {
+        if (isHeat && heat != null) {
+            map.removeLayer(heat);
+            isHeat = false;
+        } else {
+            var data = getPoints();
+            heat = L.heatLayer(data);
+            isHeat = true;
+            heat.addTo(map);
+        }
+    }
+}
+
+function getPoints() {
+    var data = points.toGeoJSON().features;
+    var heatData = [];
+    for (point in data) {
+        heatData.push([data[point].geometry.coordinates[1], data[point].geometry.coordinates[0], intensity]);
+    }
+
+    return heatData;
+}
diff --git a/javascript/leaflet-heat.js b/javascript/leaflet-heat.js
new file mode 100644
index 0000000..aa8031a
--- /dev/null
+++ b/javascript/leaflet-heat.js
@@ -0,0 +1,11 @@
+/*
+ (c) 2014, Vladimir Agafonkin
+ simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas
+ https://github.com/mourner/simpleheat
+*/
+!function(){"use strict";function t(i){return this instanceof t?(this._canvas=i="string"==typeof i?document.getElementById(i):i,this._ctx=i.getContext("2d"),this._width=i.width,this._height=i.height,this._max=1,void this.clear()):new t(i)}t.prototype={defaultRadius:25,defaultGradient:{.4:"blue",.6:"cyan",.7:"lime",.8:"yellow",1:"red"},data:function(t,i){return this._data=t,this},max:function(t){return this._max=t,this},add:function(t){return this._data.push(t),this},clear:function(){return this._data=[],this},radius:function(t,i){i=i||15;var a=this._circle=document.createElement("canvas"),s=a.getContext("2d"),e=this._r=t+i;return a.width=a.height=2*e,s.shadowOffsetX=s.shadowOffsetY=200,s.shadowBlur=i,s.shadowColor="black",s.beginPath(),s.arc(e-200,e-200,t,0,2*Math.PI,!0),s.closePath(),s.fill(),this},gradient:function(t){var i=document.createElement("canvas"),a=i.getContext("2d"),s=a.createLinearGradient(0,0,0,256);i.width=1,i.height=256;for(var e in t)s.addColorStop(e,t[e]);return a.fillStyle=s,a.fillRect(0,0,1,256),this._grad=a.getImageData(0,0,1,256).data,this},draw:function(t){this._circle||this.radius(this.defaultRadius),this._grad||this.gradient(this.defaultGradient);var i=this._ctx;i.clearRect(0,0,this._width,this._height);for(var a,s=0,e=this._data.length;e>s;s++)a=this._data[s],i.globalAlpha=Math.max(a[2]/this._max,t||.05),i.drawImage(this._circle,a[0]-this._r,a[1]-this._r);var n=i.getImageData(0,0,this._width,this._height);return this._colorize(n.data,this._grad),i.putImageData(n,0,0),this},_colorize:function(t,i){for(var a,s=3,e=t.length;e>s;s+=4)a=4*t[s],a&&(t[s-3]=i[a],t[s-2]=i[a+1],t[s-1]=i[a+2])}},window.simpleheat=t}(),/*
+ (c) 2014, Vladimir Agafonkin
+ Leaflet.heat, a tiny and fast heatmap plugin for Leaflet.
+ https://github.com/Leaflet/Leaflet.heat
+*/
+L.HeatLayer=(L.Layer?L.Layer:L.Class).extend({initialize:function(t,i){this._latlngs=t,L.setOptions(this,i)},setLatLngs:function(t){return this._latlngs=t,this.redraw()},addLatLng:function(t){return this._latlngs.push(t),this.redraw()},setOptions:function(t){return L.setOptions(this,t),this._heat&&this._updateOptions(),this.redraw()},redraw:function(){return!this._heat||this._frame||this._map._animating||(this._frame=L.Util.requestAnimFrame(this._redraw,this)),this},onAdd:function(t){this._map=t,this._canvas||this._initCanvas(),t._panes.overlayPane.appendChild(this._canvas),t.on("moveend",this._reset,this),t.options.zoomAnimation&&L.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._canvas),t.off("moveend",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},_initCanvas:function(){var t=this._canvas=L.DomUtil.create("canvas","leaflet-heatmap-layer leaflet-layer"),i=L.DomUtil.testProp(["transformOrigin","WebkitTransformOrigin","msTransformOrigin"]);t.style[i]="50% 50%";var a=this._map.getSize();t.width=a.x,t.height=a.y;var s=this._map.options.zoomAnimation&&L.Browser.any3d;L.DomUtil.addClass(t,"leaflet-zoom-"+(s?"animated":"hide")),this._heat=simpleheat(t),this._updateOptions()},_updateOptions:function(){this._heat.radius(this.options.radius||this._heat.defaultRadius,this.options.blur),this.options.gradient&&this._heat.gradient(this.options.gradient),this.options.max&&this._heat.max(this.options.max)},_reset:function(){var t=this._map.containerPointToLayerPoint([0,0]);L.DomUtil.setPosition(this._canvas,t);var i=this._map.getSize();this._heat._width!==i.x&&(this._canvas.width=this._heat._width=i.x),this._heat._height!==i.y&&(this._canvas.height=this._heat._height=i.y),this._redraw()},_redraw:function(){var t,i,a,s,e,n,h,o,r,d=[],_=this._heat._r,l=this._map.getSize(),m=new L.Bounds(L.point([-_,-_]),l.add([_,_])),c=void 0===this.options.max?1:this.options.max,u=void 0===this.options.maxZoom?this._map.getMaxZoom():this.options.maxZoom,f=1/Math.pow(2,Math.max(0,Math.min(u-this._map.getZoom(),12))),g=_/2,p=[],v=this._map._getMapPanePos(),w=v.x%g,y=v.y%g;for(t=0,i=this._latlngs.length;i>t;t++)if(a=this._map.latLngToContainerPoint(this._latlngs[t]),m.contains(a)){e=Math.floor((a.x-w)/g)+2,n=Math.floor((a.y-y)/g)+2;var x=void 0!==this._latlngs[t].alt?this._latlngs[t].alt:void 0!==this._latlngs[t][2]?+this._latlngs[t][2]:1;r=x*f,p[n]=p[n]||[],s=p[n][e],s?(s[0]=(s[0]*s[2]+a.x*r)/(s[2]+r),s[1]=(s[1]*s[2]+a.y*r)/(s[2]+r),s[2]+=r):p[n][e]=[a.x,a.y,r]}for(t=0,i=p.length;i>t;t++)if(p[t])for(h=0,o=p[t].length;o>h;h++)s=p[t][h],s&&d.push([Math.round(s[0]),Math.round(s[1]),Math.min(s[2],c)]);this._heat.data(d).draw(this.options.minOpacity),this._frame=null},_animateZoom:function(t){var i=this._map.getZoomScale(t.zoom),a=this._map._getCenterOffset(t.center)._multiplyBy(-i).subtract(this._map._getMapPanePos());L.DomUtil.setTransform?L.DomUtil.setTransform(this._canvas,a,i):this._canvas.style[L.DomUtil.TRANSFORM]=L.DomUtil.getTranslateString(a)+" scale("+i+")"}}),L.heatLayer=function(t,i){return new L.HeatLayer(t,i)};
\ No newline at end of file
diff --git a/javascript/mapChange.js b/javascript/mapChange.js
index 562947f..7434bec 100644
--- a/javascript/mapChange.js
+++ b/javascript/mapChange.js
@@ -1,50 +1,8 @@
 // Variabler:
 
-// Punktene brukt i visningen av "kart 2":
-var points = {
-  "type": "FeatureCollection",
-  "features": [
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4044907, 63.4173049]}, "properties": {"category": "Universitet", "name": "NTNU Gløshaugen"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4696324, 63.4079572]}, "properties": {"category": "Universitet", "name": "NTNU Dragvoll"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.388918, 63.4203714]}, "properties": {"category": "Universitet", "name": "NTNU Øya"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4347967, 63.4235678]}, "properties": {"category": "Universitet", "name": "NTNU Tyholt"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4143153, 63.433756]}, "properties": {"category": "Universitet", "name": "NTNU Solsiden"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4336351, 63.4138729]}, "properties": {"category": "Universitet", "name": "NTNU Moholt"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4032871, 63.4340888]}, "properties": {"category": "Universitet", "name": "NTNU Olavskvartalet"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4071882, 63.4107064]}, "properties": {"category": "Universitet", "name": "NTNU Lerkendal og Valgrinda"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3861107, 63.4288662]}, "properties": {"category": "Universitet", "name": "NTNU Kalvskinnet"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4540302, 63.4473022]}, "properties": {"category": "Universitet", "name": "NTNU Ringve"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3483128, 63.4415347]}, "properties": {"category": "Universitet", "name": "NTNU Heggdalen"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4492127, 63.4521411]}, "properties": {"category": "Universitet", "name": "NTNU Østmarka"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3981586, 63.4373727]}, "properties": {"category": "Universitet", "name": "NTNU Brattørkaia"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3992594, 63.4152944]}, "properties": {"category": "Universitet", "name": "NTNU Elgeseter"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4837531, 63.4393659]}, "properties": {"category": "Universitet", "name": "NTNU Rotvoll"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4660445, 63.42338]}, "properties": {"category": "Universitet", "name": "NTNU Tunga"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4176387, 63.4141732]}, "properties": {"category": "Studentby", "name": "Berg Studentby"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4301549, 63.4110984]}, "properties": {"category": "Studentby", "name": "Moholt Studentby"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4372857, 63.3989126]}, "properties": {"category": "Studentby", "name": "Steinan Studentby"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3918823, 63.420511]}, "properties": {"category": "Studentby", "name": "Bloksberg Studentby"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4382451, 63.4116778]}, "properties": {"category": "Studentby", "name": "Karinelund Studentby"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4945864, 63.4211031]}, "properties": {"category": "Studentby", "name": "Jakobsliveien 55"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.396918, 63.4226982]}, "properties": {"category": "Studentby", "name": "Klostergata 18"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3963873, 63.4227505]}, "properties": {"category": "Studentby", "name": "Klostergata 20"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3891634, 63.4232409]}, "properties": {"category": "Studentby", "name": "Klostergata 56"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4126211, 63.4147073]}, "properties": {"category": "Studentby", "name": "Nedre Berg Studentby"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3973319, 63.418334]}, "properties": {"category": "Studentby", "name": "Magnus den godes gate 2"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4037983, 63.4215247]}, "properties": {"category": "Studentby", "name": "Nedre Singsakerslette"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3890993, 63.4276236]}, "properties": {"category": "Studentby", "name": "Sverresgate 8"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4122453, 63.4337663]}, "properties": {"category": "Studentby", "name": "Nedre elvehavn Studentby"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3982518, 63.4163341]}, "properties": {"category": "Studentby", "name": "Teknobyen Studentboliger"}},
-    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4003657, 63.4117822]}, "properties": {"category": "Studentby", "name": "Lerkendal Studentby"}}
-  ]
-};
-
 var m = "m1"; // Hvilket kart en bruker
 var layersOnMap = []; // Hvilke kartlag som var på i "m1" før en byttet til "m2"
 
-// Legger til alle punktene over og setter på en popup med info-tekst:
-var NTNU_points = L.geoJSON(points).bindPopup(function(point) {return "<b>" + point.feature.properties.category + "</b>" + "<br>" + point.feature.properties.name;})
-
 // Bytter layout på nettsiden ved kartbytte
 
 function changeMap() {
@@ -66,8 +24,9 @@ function changeMap() {
 
     // Endrer knappene nedrest til høyre i kartet:
     document.getElementById("sidebarOpener").style.display = "none";
-    document.getElementById("mapChanger").style.marginLeft = "0";
-
+    document.getElementById("pointOpener").style.display = "block";
+    document.getElementById("addPointButton").style.display = "block";
+    
     // Skjuler synlige kartlag i kartet:
     for (key in overlayMaps) {
       var layer = overlayMaps[key];
@@ -76,9 +35,6 @@ function changeMap() {
         layersOnMap.push(key);
       }
     }
-
-    // Legger til nye punkt-markører i kartet:
-    NTNU_points.addTo(map);
   } else if (m == "m2") {
     m = "m1";
 
@@ -88,18 +44,25 @@ function changeMap() {
 
     // Endrer knappene nedrest til høyre i kartet:
     document.getElementById("sidebarOpener").style.display = "block";
-    document.getElementById("mapChanger").style.marginLeft = "8vh";
+    document.getElementById("pointOpener").style.display = "none";
+    document.getElementById("addPointButton").style.display = "none";
 
     // Fjerner punkt-markørene og eventuelle voronoi- og TIN-diagram fra kartet:
-    if (map.hasLayer(NTNU_points)) {
-        map.removeLayer(NTNU_points);
-    }
     if (isVoronoi) {
-        voronoi();
+      voronoi();
     }
     if (isTIN) {
         TIN();
     }
+    if (isHeat) {
+      heatmap();
+    }
+
+    if (points != null) {
+      if (map.hasLayer(points)) {
+          handleDefaultPoints();
+      }
+    }
 
     // Legger til kartlagene som lå i kartet før en byttet kartmodus:
     while (layersOnMap.length > 0) {
diff --git a/javascript/pointMap.js b/javascript/pointMap.js
new file mode 100644
index 0000000..6e50849
--- /dev/null
+++ b/javascript/pointMap.js
@@ -0,0 +1,218 @@
+// Punktene brukt i visningen av "kart 2":
+/*
+var NTNU_points = {
+    "type": "FeatureCollection",
+    "features": [
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4044907, 63.4173049]}, "properties": {"category": "Universitet", "name": "NTNU Gløshaugen"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4696324, 63.4079572]}, "properties": {"category": "Universitet", "name": "NTNU Dragvoll"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.388918, 63.4203714]}, "properties": {"category": "Universitet", "name": "NTNU Øya"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4347967, 63.4235678]}, "properties": {"category": "Universitet", "name": "NTNU Tyholt"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4143153, 63.433756]}, "properties": {"category": "Universitet", "name": "NTNU Solsiden"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4336351, 63.4138729]}, "properties": {"category": "Universitet", "name": "NTNU Moholt"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4032871, 63.4340888]}, "properties": {"category": "Universitet", "name": "NTNU Olavskvartalet"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4071882, 63.4107064]}, "properties": {"category": "Universitet", "name": "NTNU Lerkendal og Valgrinda"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3861107, 63.4288662]}, "properties": {"category": "Universitet", "name": "NTNU Kalvskinnet"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4540302, 63.4473022]}, "properties": {"category": "Universitet", "name": "NTNU Ringve"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3483128, 63.4415347]}, "properties": {"category": "Universitet", "name": "NTNU Heggdalen"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4492127, 63.4521411]}, "properties": {"category": "Universitet", "name": "NTNU Østmarka"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3981586, 63.4373727]}, "properties": {"category": "Universitet", "name": "NTNU Brattørkaia"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3992594, 63.4152944]}, "properties": {"category": "Universitet", "name": "NTNU Elgeseter"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4837531, 63.4393659]}, "properties": {"category": "Universitet", "name": "NTNU Rotvoll"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4660445, 63.42338]}, "properties": {"category": "Universitet", "name": "NTNU Tunga"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4176387, 63.4141732]}, "properties": {"category": "Studentby", "name": "Berg Studentby"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4301549, 63.4110984]}, "properties": {"category": "Studentby", "name": "Moholt Studentby"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4372857, 63.3989126]}, "properties": {"category": "Studentby", "name": "Steinan Studentby"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3918823, 63.420511]}, "properties": {"category": "Studentby", "name": "Bloksberg Studentby"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4382451, 63.4116778]}, "properties": {"category": "Studentby", "name": "Karinelund Studentby"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4945864, 63.4211031]}, "properties": {"category": "Studentby", "name": "Jakobsliveien 55"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.396918, 63.4226982]}, "properties": {"category": "Studentby", "name": "Klostergata 18"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3963873, 63.4227505]}, "properties": {"category": "Studentby", "name": "Klostergata 20"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3891634, 63.4232409]}, "properties": {"category": "Studentby", "name": "Klostergata 56"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4126211, 63.4147073]}, "properties": {"category": "Studentby", "name": "Nedre Berg Studentby"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3973319, 63.418334]}, "properties": {"category": "Studentby", "name": "Magnus den godes gate 2"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4037983, 63.4215247]}, "properties": {"category": "Studentby", "name": "Nedre Singsakerslette"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3890993, 63.4276236]}, "properties": {"category": "Studentby", "name": "Sverresgate 8"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4122453, 63.4337663]}, "properties": {"category": "Studentby", "name": "Nedre elvehavn Studentby"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.3982518, 63.4163341]}, "properties": {"category": "Studentby", "name": "Teknobyen Studentboliger"}},
+        {"type": "Feature", "geometry": {"type": "Point", "coordinates": [10.4003657, 63.4117822]}, "properties": {"category": "Studentby", "name": "Lerkendal Studentby"}}
+    ]
+};
+*/
+
+// Initialiserer punkt-laget:
+var points = null;
+
+var newPoint = false;
+
+map.addEventListener('click', (event) => {
+    if (newPoint) {
+        onMapClick(event);
+    }
+})
+
+function addPoint() {
+    document.getElementById("addPointButton").style.color = "green";
+    newPoint = true;
+
+    if (isTIN) {
+        TIN();
+    }
+    if (isVoronoi) {
+        voronoi();
+    }
+}
+
+function onMapClick(e) {
+    openBox("makeNewPointBox");
+    document.getElementById("lengdegrader").value = e.latlng.lng;
+    document.getElementById("breddegrader").value = e.latlng.lat;
+
+    document.getElementById("addPointButton").style.color = "orangered";
+    newPoint = false;
+}
+
+function savePoint() {
+    var regex = /^[a-zA-Z_0-9]+$/;
+
+    if (document.getElementById("pointCategory").value == "") {
+        return alert("You need to choose a category!");
+    } else if (!document.getElementById("pointCategory").value.match(regex)) {
+        return alert("The category must consist of normal letters!");
+    } else if (document.getElementById("pointName").value == "") {
+        return alert("You need to choose a name!");
+    } else if (!document.getElementById("pointName").value.match(regex)) {
+        return alert("The name must consist of normal letters!");
+    }
+
+    var lon = document.getElementById("lengdegrader").value;
+    var lat = document.getElementById("breddegrader").value;
+    var category = document.getElementById("pointCategory").value;
+    var name = document.getElementById("pointName").value;
+
+    if (points == null) {
+        var content = {"type": "FeatureCollection", "features": []}
+        var newPoint = {"type": "Feature", "geometry": {"type": "Point", "coordinates": [lon, lat]}, "properties": {"category": category, "name": name}};
+        content.features.push(newPoint);
+        points = L.geoJSON(content).bindPopup(function(point) {return "<b>" + point.feature.properties.category + "</b>" + "<br>" + point.feature.properties.name});
+        points.addTo(map);
+        pointsExists();
+    } else {
+        var newPoint = {"type": "Feature", "geometry": {"type": "Point", "coordinates": [lon, lat]}, "properties": {"category": category, "name": name}};
+        var content = points.toGeoJSON();
+        content.features.push(newPoint);
+        map.removeLayer(points);
+        points = L.geoJSON(content).bindPopup(function(point) {return "<b>" + point.feature.properties.category + "</b>" + "<br>" + point.feature.properties.name});
+        points.addTo(map);
+    }
+
+    document.getElementById("pointCategory").value = "";
+    document.getElementById("pointName").value = "";
+    closeBox("makeNewPointBox");
+}
+
+function handleDefaultPoints() {
+    if (points != null) {
+        if (map.hasLayer(points)){
+            document.getElementById("examplePoints").style.display = "inline-block";
+            document.getElementById("loadPoints").style. display = "inline-block";
+            document.getElementById("savePoints").style.display = "none";
+            document.getElementById("fileName").style.display = "none";
+            document.getElementById("save").style.display = "none";
+            document.getElementById("removePoints").style.display = "none";
+            
+            if (isVoronoi) {
+                voronoi();
+            }
+            if (isTIN) {
+                TIN();
+            }
+            if (isHeat) {
+                heatmap();
+            }
+
+            // Fjerner punktene:
+            map.removeLayer(points);
+            points = null;
+
+            closeBox('loadPointsBox')
+        }
+    } else {
+        pointsExists();
+
+        // Legger til alle punktene fra default-fila og setter på en popup med info-tekst:
+        
+        
+        fetch("javascript/exampleData/points.geojson").then(function(response) {
+            return response.json();
+        }).then(function(data) {
+            points.addData(data).bindPopup(function(point) {return "<b>" + point.feature.properties.category + "</b>" + "<br>" + point.feature.properties.name});
+        })
+        
+
+        //points = L.geoJSON(NTNU_points).bindPopup(function(point) {return `<b>${point.feature.properties.category}</b><br>${point.feature.properties.name}`});
+        
+        // Legger til nye punkt-markører i kartet:
+        points.addTo(map);
+
+        closeBox('loadPointsBox')
+    }
+}
+
+function loadPoints() {
+    document.getElementById("fileInput2").click();
+}
+
+document.getElementById("fileInput2").addEventListener("change", () => {
+    var selectedFile = document.getElementById("fileInput2").files[0];
+
+    if (selectedFile != null) {
+        document.getElementById("fileInput2").value = "";
+
+        var read = new FileReader();
+        read.readAsDataURL(selectedFile);
+
+        var newLayer = L.geoJSON();
+
+        read.onloadend = function() {
+            fetch(read.result).then(function(response) {
+                return response.json();
+            }).then(function(data) {
+                newLayer.addData(data).bindPopup(function(point) {return "<b>" + point.feature.properties.category + "</b>" + "<br>" + point.feature.properties.name});
+            })
+        }
+    }
+
+    points = newLayer;
+    points.addTo(map);
+
+    pointsExists();
+    closeBox('loadPointsBox');
+})
+
+function pointsExists() {
+    document.getElementById("examplePoints").style.display = "none";
+    document.getElementById("loadPoints").style. display = "none";
+    document.getElementById("savePoints").style.display = "inline-block";
+    document.getElementById("fileName").style.display = "inline-block";
+    document.getElementById("save").style.display = "inline-block";
+    document.getElementById("removePoints").style.display = "inline-block";
+}
+
+function saveToFile() {
+    var regex = /^[a-zA-Z_0-9]+$/;
+
+    if (document.getElementById("fileName").value == "") {
+        return alert("You need to choose a name!");
+    } else if (!document.getElementById("fileName").value.match(regex)) {
+        return alert("The category must consist of normal letters!");
+    }
+    
+    var geoJSON = points.toGeoJSON();
+    var fileName = document.getElementById("fileName").value;
+    var file = fileName + ".geojson";
+    saveAs(new File([JSON.stringify(geoJSON)], file, {
+        type: "text/plain;charset=utf-8"
+    }), file);
+
+    document.getElementById("fileName").value = "";
+}
diff --git a/javascript/tin.js b/javascript/tin.js
index fef7d85..26f2386 100644
--- a/javascript/tin.js
+++ b/javascript/tin.js
@@ -2,17 +2,22 @@
 
 var isTIN = false // Vises TIN'et i kartet?
 
-// Selve TIN'et:
-var TINPolygons = turf.tin(NTNU_points.toGeoJSON());
-var TINGeoJSON = L.geoJSON(TINPolygons, {style: {"color": "orangered"}});
+var TINPolygons = null;
+var TINGeoJSON = null;
 
 // Funksjon for å slå av og på TIN'et:
 function TIN() {
-    if (isTIN) {
-        map.removeLayer(TINGeoJSON);
-        isTIN = false;
-    } else {
-        TINGeoJSON.addTo(map);
-        isTIN = true;
+    if (points != null) {
+        if (isTIN && TINGeoJSON != null) {
+            map.removeLayer(TINGeoJSON);
+            isTIN = false;
+        } else {
+            // Selve TIN'et:
+            TINPolygons = turf.tin(points.toGeoJSON());
+            TINGeoJSON = L.geoJSON(TINPolygons, {style: {"color": "orangered"}});
+
+            TINGeoJSON.addTo(map);
+            isTIN = true;
+        }
     }
 }
diff --git a/javascript/voronoi.js b/javascript/voronoi.js
index 9e51de7..df39829 100644
--- a/javascript/voronoi.js
+++ b/javascript/voronoi.js
@@ -2,21 +2,50 @@
 
 var isVoronoi = false; // Vises voronoi-diagrammet i kartet?
 
-// Avgrensning for voronoi-diagrammet
-var options = {
-    bbox: [10.3175248, 63.3911153, 10.5242766, 63.4570029]
-};
-
-// Selve voronoi-diagrammet:
-var voronoiPolygons = turf.voronoi(NTNU_points.toGeoJSON(), options);
-var voronoiGeoJSON = L.geoJSON(voronoiPolygons, {style: {"color": "orange"}});
+var voronoiPolygons = null;
+var voronoiGeoJSON = null;
 
 function voronoi() {  // Skrur av og på voronoi-diagrammet i kartet
-    if (isVoronoi) {
-        map.removeLayer(voronoiGeoJSON)
-        isVoronoi = false;
-    } else {
-        voronoiGeoJSON.addTo(map);
-        isVoronoi = true;
+    if (points != null) {
+        if (isVoronoi && voronoiGeoJSON != null) {
+            map.removeLayer(voronoiGeoJSON)
+            isVoronoi = false;
+        } else {
+
+            // Avgrensning for voronoi-diagrammet
+
+            var [minlon, minlat, maxlon, maxlat] = findMinMax();
+
+            var options = {
+                bbox: [minlon, minlat, maxlon, maxlat]
+            };
+
+            // Selve voronoi-diagrammet:
+            voronoiPolygons = turf.voronoi(points.toGeoJSON(), options);
+            voronoiGeoJSON = L.geoJSON(voronoiPolygons, {style: {"color": "orange"}});
+
+            voronoiGeoJSON.addTo(map);
+            isVoronoi = true;
+        }
+    }
+}
+
+function findMinMax() {
+    var [minlon, minlat, maxlon, maxlat] = [99, 99, -99, -99];
+    
+    for (feature in points.toGeoJSON().features) {
+        if (points.toGeoJSON().features[feature].geometry.coordinates[0] < minlon) {
+            minlon = points.toGeoJSON().features[feature].geometry.coordinates[0];
+        }
+        if (points.toGeoJSON().features[feature].geometry.coordinates[0] > maxlon) {
+            maxlon = points.toGeoJSON().features[feature].geometry.coordinates[0];
+        }
+        if (points.toGeoJSON().features[feature].geometry.coordinates[1] < minlat) {
+            minlat = points.toGeoJSON().features[feature].geometry.coordinates[1];
+        }
+        if (points.toGeoJSON().features[feature].geometry.coordinates[1] > maxlat) {
+            maxlat = points.toGeoJSON().features[feature].geometry.coordinates[1];
+        }
     }
+    return [minlon - 0.5, minlat - 0.5, maxlon + 0.5, maxlat + 0.5];
 }
-- 
GitLab