diff --git a/pom.xml b/pom.xml
index f47d0f176c8d12fb4fcf1a816044c878b88a91da..e372339da515df616f6d0ebcc41bc1d67186e204 100755
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
     <groupId>no.nibio.vips.</groupId>
     <artifactId>VIPSLogic</artifactId>
     <packaging>war</packaging>
-    <version>2024.2</version>
+    <version>2024.3</version>
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
diff --git a/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java b/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java
index b7277f97d8fac27234bf423a10618e03372d33e9..5685c07d82e41661b1d21ba65b153c9792cf3dac 100755
--- a/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java
+++ b/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java
@@ -18,6 +18,7 @@
 
 package no.nibio.vips.logic.controller.servlet;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.ibm.icu.util.Calendar;
 import java.io.IOException;
 import java.text.ParseException;
@@ -35,14 +36,14 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.LoggerFactory;
+
 import no.nibio.vips.logic.controller.session.ForecastBean;
 import no.nibio.vips.logic.controller.session.OrganismBean;
+import no.nibio.vips.logic.controller.session.PointOfInterestBean;
 import no.nibio.vips.logic.controller.session.UserBean;
-import no.nibio.vips.logic.entity.ForecastConfiguration;
-import no.nibio.vips.logic.entity.ModelInformation;
-import no.nibio.vips.logic.entity.Organization;
-import no.nibio.vips.logic.entity.VipsLogicRole;
-import no.nibio.vips.logic.entity.VipsLogicUser;
+import no.nibio.vips.logic.entity.*;
 import no.nibio.vips.logic.i18n.SessionLocaleUtil;
 import no.nibio.vips.logic.util.Globals;
 import no.nibio.vips.logic.util.SystemTime;
@@ -52,6 +53,9 @@ import no.nibio.web.forms.FormField;
 import no.nibio.web.forms.FormValidation;
 import no.nibio.web.forms.FormValidationException;
 import no.nibio.web.forms.FormValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.wololo.geojson.GeoJSON;
 
 /**
  * Handles form configuration actions
@@ -59,7 +63,9 @@ import no.nibio.web.forms.FormValidator;
  * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
  */
 public class ForecastConfigurationController extends HttpServlet {
-    
+
+    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ForecastConfigurationController.class);
+
     @PersistenceContext(unitName="VIPSLogic-PU")
     EntityManager em;
     
@@ -72,6 +78,9 @@ public class ForecastConfigurationController extends HttpServlet {
     @EJB
     OrganismBean organismBean;
 
+    @EJB
+    PointOfInterestBean pointOfInterestBean;
+
     /**
      * Processes requests for both HTTP <code>GET</code> and <code>POST</code>
      * methods.
@@ -203,7 +212,7 @@ public class ForecastConfigurationController extends HttpServlet {
                     if(forecastConfiguration == null)
                     {
                         forecastConfiguration = new ForecastConfiguration();
-                        multipleNew =   request.getParameter("multipleNew") != null 
+                        multipleNew =   request.getParameter("multipleNew") != null
                                         && request.getParameter("multipleNew").equals("true");
                     }
                     // Only superusers can view and edit forecasts from other organizations
@@ -221,14 +230,22 @@ public class ForecastConfigurationController extends HttpServlet {
                         // TODO: More intelligent selection of locations, weather stations and users
                         if(user.isSuperUser())
                         {
-                            request.setAttribute("locationPointOfInterests", em.createNamedQuery("PointOfInterest.findAll").getResultList());
-                            request.setAttribute("weatherStationPointOfInterests", em.createNamedQuery("PointOfInterestWeatherStation.findAllByActivity").setParameter("active", Boolean.TRUE).getResultList());
+                            List<PointOfInterest> poiList = em.createNamedQuery("PointOfInterest.findAll").getResultList();
+                            request.setAttribute("locationPointOfInterests", poiList);
+                            request.setAttribute("locationPointOfInterestsGeoJson", convertToGeoJson(poiList));
+                            List<PointOfInterest> weatherStationPoiList = em.createNamedQuery("PointOfInterestWeatherStation.findAllByActivity").setParameter("active", Boolean.TRUE).getResultList();
+                            request.setAttribute("weatherStationPointOfInterests", weatherStationPoiList);
+                            request.setAttribute("weatherStationPointOfInterestsGeoJson", convertToGeoJson(weatherStationPoiList));
                             request.setAttribute("vipsLogicUsers", em.createNamedQuery("VipsLogicUser.findAll").getResultList());
                         }
                         else
                         {
-                            request.setAttribute("locationPointOfInterests", em.createNamedQuery("PointOfInterest.findByOrganizationId").setParameter("organizationId", user.getOrganizationId()).getResultList());
-                            request.setAttribute("weatherStationPointOfInterests", em.createNamedQuery("PointOfInterestWeatherStation.findByActivityAndOrganizationId").setParameter("active", Boolean.TRUE).setParameter("organizationId", user.getOrganizationId()).getResultList());
+                            List<PointOfInterest> poiList = em.createNamedQuery("PointOfInterest.findByOrganizationId").setParameter("organizationId", user.getOrganizationId()).getResultList();
+                            request.setAttribute("locationPointOfInterests", poiList);
+                            request.setAttribute("locationPointOfInterestsGeoJson", convertToGeoJson(poiList));
+                            List<PointOfInterest> weatherStationPoiList = em.createNamedQuery("PointOfInterestWeatherStation.findByActivityAndOrganizationId").setParameter("active", Boolean.TRUE).setParameter("organizationId", user.getOrganizationId()).getResultList();
+                            request.setAttribute("weatherStationPointOfInterests", weatherStationPoiList);
+                            request.setAttribute("weatherStationPointOfInterestsGeoJson", convertToGeoJson(weatherStationPoiList));
                             request.setAttribute("vipsLogicUsers", em.createNamedQuery("VipsLogicUser.findByOrganizationId").setParameter("organizationId", user.getOrganizationId()).getResultList());
                         }
                         request.setAttribute("forecastConfiguration", forecastConfiguration);
@@ -244,7 +261,7 @@ public class ForecastConfigurationController extends HttpServlet {
                         request.setAttribute("modelInformations", forecastBean.getBatchableModels());
                         request.setAttribute("messageKey", request.getParameter("messageKey"));
                         request.getRequestDispatcher("/forecastConfigurationForm.ftl").forward(request, response);
-                       
+
                     }
                 }
                 catch(NullPointerException | NumberFormatException ex)
@@ -264,13 +281,17 @@ public class ForecastConfigurationController extends HttpServlet {
                         forecastConfiguration.setIsPrivate(Boolean.TRUE);
                     }
                     request.setAttribute("forecastConfiguration", forecastConfiguration);
-                    request.setAttribute("locationPointOfInterests", em.createNamedQuery("PointOfInterest.findByOrganizationId").setParameter("organizationId", user.getOrganizationId()).getResultList());
-                    request.setAttribute("weatherStationPointOfInterests", em.createNamedQuery("PointOfInterestWeatherStation.findByActivityAndOrganizationId").setParameter("active", Boolean.TRUE).setParameter("organizationId", user.getOrganizationId()).getResultList());
+                    List<PointOfInterest> poiList = em.createNamedQuery("PointOfInterest.findByOrganizationId").setParameter("organizationId", user.getOrganizationId()).getResultList();
+                    request.setAttribute("locationPointOfInterests", poiList);
+                    request.setAttribute("locationPointOfInterestsGeoJson", convertToGeoJson(poiList));
+                    List<PointOfInterest> weatherStationPoiList = em.createNamedQuery("PointOfInterestWeatherStation.findByActivityAndOrganizationId").setParameter("active", Boolean.TRUE).setParameter("organizationId", user.getOrganizationId()).getResultList();
+                    request.setAttribute("weatherStationPointOfInterests", weatherStationPoiList);
+                    request.setAttribute("weatherStationPointOfInterestsGeoJson", convertToGeoJson(weatherStationPoiList));
                     request.setAttribute("forecastConfiguration", forecastConfiguration);
                     request.setAttribute("formId","forecastConfigurationForm");
                     request.getSession().setAttribute("availableTimeZones", SystemTime.getAvailableTimeZones());
                     request.getSession().setAttribute("defaultTimeZoneId", user.getOrganizationId().getDefaultTimeZone());
-                    
+
                     request.setAttribute("allCrops", em.createNamedQuery("Organism.findAllCrops").getResultList());
                     request.setAttribute("allPests", em.createNamedQuery("Organism.findAllPests").getResultList());
                     // Hierarchy categories
@@ -279,13 +300,13 @@ public class ForecastConfigurationController extends HttpServlet {
                     request.setAttribute("messageKey", request.getParameter("messageKey"));
                     request.getRequestDispatcher("/forecastConfigurationForm.ftl").forward(request, response);
                 }
-                else 
+                else
                 {
                     response.sendError(403,"Access not authorized");
                 }
             }
         }
-        
+
         // Store forecast configuration(s)
         // Authorization: SUPERUSERS and ORGANIZATION ADMINS
         else if(action.equals("forecastConfigurationFormSubmit"))
@@ -310,14 +331,17 @@ public class ForecastConfigurationController extends HttpServlet {
                 }
                 else
                 {
-                    String formId = request.getParameter("multipleNew") != null && request.getParameter("multipleNew").equals("true") ? 
-                            "forecastConfigurationMultipleNewForm" 
+                    String formId = request.getParameter("multipleNew") != null && request.getParameter("multipleNew").equals("true") ?
+                            "forecastConfigurationMultipleNewForm"
                             :"forecastConfigurationForm";
                     FormValidation formValidation = FormValidator.validateForm(formId,request,getServletContext());
+                    LOGGER.debug("formValidation=" + formValidation.isValid());
                     // Also validation the model specific fields
                     String modelId = formValidation.getFormField("modelId").getWebValue();
                     FormValidation modelFieldFormValidation = FormValidator.validateForm("models/" + modelId, request, getServletContext());
 
+                    // Additional input check: If the Grid data checkbox is not checked, a
+
                     if(formValidation.isValid() && modelFieldFormValidation.isValid())
                     {
                         if(formId.equals("forecastConfigurationForm"))
@@ -344,7 +368,7 @@ public class ForecastConfigurationController extends HttpServlet {
                             for(String optionVal:formValidation.getFormField("weatherStationPointOfInterestIds").getWebValues())
                             {
                                 Integer weatherStationPointOfInterestId = Integer.valueOf(optionVal);
-                                forecastBean.storeNewMultipleForecastConfiguration(weatherStationPointOfInterestId, formValidation.getFormFields(), modelFieldFormValidation.getFormFields());    
+                                forecastBean.storeNewMultipleForecastConfiguration(weatherStationPointOfInterestId, formValidation.getFormFields(), modelFieldFormValidation.getFormFields());
                             }
                             request.setAttribute("messageKey", request.getParameter("multipleForecastConfigurationsCreated"));
                             response.sendRedirect(new StringBuilder(Globals.PROTOCOL + "://").append(ServletUtil.getServerName(request)).append("/forecastConfiguration?messageKey=").append("multipleForecastConfigurationsCreated").toString());
@@ -357,8 +381,12 @@ public class ForecastConfigurationController extends HttpServlet {
                         // We must get date formats!
                         Map<String, FormField> formFields = FormValidator.getFormFields("forecastConfigurationForm",getServletContext());
                         // TODO: More intelligent selection of locations, weather stations and users
-                        request.setAttribute("locationPointOfInterests", em.createNamedQuery("PointOfInterest.findAll").getResultList());
-                        request.setAttribute("weatherStationPointOfInterests", em.createNamedQuery("PointOfInterestWeatherStation.findAll").getResultList());
+                        List<PointOfInterest> poiList = em.createNamedQuery("PointOfInterest.findAll").getResultList();
+                        request.setAttribute("locationPointOfInterests", poiList);
+                        request.setAttribute("locationPointOfInterestsGeoJson", convertToGeoJson(poiList));
+                        List<PointOfInterest> weatherStationPoiList = em.createNamedQuery("PointOfInterestWeatherStation.findAll").getResultList();
+                        request.setAttribute("weatherStationPointOfInterests", weatherStationPoiList);
+                        request.setAttribute("weatherStationPointOfInterestsGeoJson", convertToGeoJson(weatherStationPoiList));
                         request.setAttribute("vipsLogicUsers", em.createNamedQuery("VipsLogicUser.findAll").getResultList());
                         request.setAttribute("dateStart_dateFormat", formFields.get("dateStart").getDateFormat());
                         request.setAttribute("dateEnd_dateFormat", formFields.get("dateEnd").getDateFormat());
@@ -403,13 +431,13 @@ public class ForecastConfigurationController extends HttpServlet {
                 try
                 {
                     forecastBean.deleteForecastConfiguration(forecastConfigurationId);
-                    response.sendRedirect(new StringBuilder(Globals.PROTOCOL + "://").append(ServletUtil.getServerName(request)).append("/forecastConfiguration?").append("&messageKey=").append("forecastConfigurationDeleted").toString());                       
+                    response.sendRedirect(new StringBuilder(Globals.PROTOCOL + "://").append(ServletUtil.getServerName(request)).append("/forecastConfiguration?").append("&messageKey=").append("forecastConfigurationDeleted").toString());
                 }
                 catch(NullPointerException | NumberFormatException ex)
                 {
                     response.sendError(500, "Invalid forecast configurationId " + request.getParameter("forecastConfigurationId"));
                 }
-                
+
             }
             else
             {
@@ -418,6 +446,11 @@ public class ForecastConfigurationController extends HttpServlet {
         }
     }
 
+    private GeoJSON convertToGeoJson(List<PointOfInterest> poiList) {
+        return pointOfInterestBean.getPoisAsGeoJson(poiList);
+    }
+
+
     // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
     /**
      * Handles the HTTP <code>GET</code> method.
diff --git a/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java b/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java
index 6aa46f8bfcbd5603c43220f557c84de5832f1df5..9e6cdcb1923f18e7ab419615f8f21724dcda2ee1 100755
--- a/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java
+++ b/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java
@@ -600,6 +600,7 @@ public class ForecastBean {
         forecastConfiguration.setCropOrganismId(em.find(Organism.class, formFields.get("cropOrganismId").getValueAsInteger()));
         forecastConfiguration.setPestOrganismId(em.find(Organism.class, formFields.get("pestOrganismId").getValueAsInteger()));
         forecastConfiguration.setIsPrivate(formFields.get("isPrivate").getWebValue() != null);
+        forecastConfiguration.setUseGridWeatherData(formFields.get("useGridWeatherData").getWebValue() != null);
         PointOfInterest locationPoi = em.find(PointOfInterest.class, formFields.get("locationPointOfInterestId").getValueAsInteger());
         forecastConfiguration.setLocationPointOfInterestId(locationPoi);
         PointOfInterest weatherStationPoi = em.find(PointOfInterestWeatherStation.class, formFields.get("weatherStationPointOfInterestId").getValueAsInteger());
diff --git a/src/main/java/no/nibio/vips/logic/controller/session/SessionControllerGetter.java b/src/main/java/no/nibio/vips/logic/controller/session/SessionControllerGetter.java
index 399bd43b255d37c6acd20106083a2fdec34849f6..be60d07964cf56c465790019eed7d95559208aa7 100644
--- a/src/main/java/no/nibio/vips/logic/controller/session/SessionControllerGetter.java
+++ b/src/main/java/no/nibio/vips/logic/controller/session/SessionControllerGetter.java
@@ -34,7 +34,7 @@ public class SessionControllerGetter {
     
 	// This obviously has to be changed when changing the application name in Maven
 	// TODO: Refactor out to System properties (e.g. in standalone.xml in JBoss/WildFly)
-    public static final String JNDI_PATH = "java:global/VIPSLogic-2024.2/";
+    public static final String JNDI_PATH = "java:global/VIPSLogic-2024.3/";
 
     public static SchedulingBean getSchedulingBean()
     {
diff --git a/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java b/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java
index a17f2fbadaa35794dd3de13e10a6883d024776f7..735eebd16da0098531a68965e87403fe1994bbb1 100755
--- a/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java
+++ b/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java
@@ -50,7 +50,7 @@ import org.hibernate.annotations.TypeDef;
 import org.hibernate.annotations.TypeDefs;
 
 /**
- * @copyright 2014-2016 <a href="http://www.nibio.no/">NIBIO</a>
+ * @copyright 2014-2024 <a href="http://www.nibio.no/">NIBIO</a>
  * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
  */
 @Entity
@@ -123,7 +123,17 @@ public class ForecastConfiguration implements Serializable, Comparable {
     private Organism pestOrganismId;
     @Column(name = "is_private")
     private Boolean isPrivate;
+    @Column(name = "use_grid_weather_data")
+    private Boolean useGridWeatherData;
     
+    public Boolean getUseGridWeatherData() {
+        return useGridWeatherData != null ? useGridWeatherData : Boolean.FALSE;
+    }
+
+    public void setUseGridWeatherData(Boolean useGridWeatherData) {
+        this.useGridWeatherData = useGridWeatherData;
+    }
+
     @Type(type = "IntegerArray")
     @Column(name = "grid_weather_station_point_of_interest_ids")
     private Integer[] gridWeatherStationPointOfInterestIds;
diff --git a/src/main/java/no/nibio/vips/logic/service/POIService.java b/src/main/java/no/nibio/vips/logic/service/POIService.java
index 19132a3b9146e71697b99567cb723a0acd7140b9..399cb26acf2078c0d0c4e9da7b48020e75605359 100644
--- a/src/main/java/no/nibio/vips/logic/service/POIService.java
+++ b/src/main/java/no/nibio/vips/logic/service/POIService.java
@@ -37,7 +37,9 @@ import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import no.nibio.vips.logic.controller.session.PointOfInterestBean;
+import no.nibio.vips.logic.entity.helpers.PointOfInterestFactory;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Point;
@@ -102,7 +104,70 @@ public class POIService {
         PointOfInterest retVal = SessionControllerGetter.getPointOfInterestBean().getPointOfInterest(pointOfInterestId);
         return Response.ok().entity(retVal).build();
     }
-    
+
+    @POST
+    @Consumes("application/json;charset=UTF-8")
+    @Produces("application/json;charset=UTF-8")
+    public Response postPoi(String poiJson) {
+        // TODO Fix authentication
+        ObjectMapper oM = new ObjectMapper();
+        Map<Object, Object> poiMap;
+        try {
+            poiMap = oM.readValue(poiJson, new TypeReference<HashMap<Object, Object>>() {});
+        } catch (JsonProcessingException e) {
+            LOGGER.error(e.getMessage(), e);
+            return Response.status(Status.BAD_REQUEST).entity("Unable to parse input").build();
+        }
+
+        Integer poiUserId = poiMap.get("userId") != null ? Integer.parseInt(poiMap.get("userId").toString()) : null;
+
+        VipsLogicUser user = SessionControllerGetter.getUserBean().getVipsLogicUser(poiUserId);
+        if (user == null) {
+            LOGGER.error("No user found for userId={}", poiUserId);
+            return Response.status(Status.UNAUTHORIZED).build();
+        }
+        LOGGER.error("Remember to check for roles as well, if necessary!");
+
+        PointOfInterestBean poiBean = SessionControllerGetter.getPointOfInterestBean();
+
+        Integer poiTypeId = poiMap.get("typeId") != null ? Integer.parseInt(poiMap.get("typeId").toString()) : null;
+        if(poiTypeId == null) {
+            return Response.status(Status.BAD_REQUEST).entity("Point of interest type is required").build();
+        }
+        String poiName = poiMap.get("name") != null ? poiMap.get("name").toString() : null;
+        Double poiLongitude = poiMap.get("longitude") != null ? Double.valueOf(poiMap.get("longitude").toString()): null;
+        Double poiLatitude = poiMap.get("latitude") != null ? Double.valueOf(poiMap.get("latitude").toString()): null;
+        Double poiAltitude = poiMap.get("altitude") != null ? Double.valueOf(poiMap.get("altitude").toString()): null;
+
+        PointOfInterest poiToSave = PointOfInterestFactory.getPointOfInterest(poiTypeId);
+        poiToSave.setName(poiName);
+        poiToSave.setLongitude(poiLongitude);
+        poiToSave.setLatitude(poiLatitude);
+        poiToSave.setAltitude(poiAltitude);
+        poiToSave.setLastEditedTime(new Date());
+        poiToSave.setUser(user);
+        poiToSave.setCountryCode(user.getOrganizationId().getCountryCode());
+
+        if (poiLongitude != null && poiLatitude != null && poiAltitude != null) {
+            GISUtil gisUtil = new GISUtil();
+            Coordinate coordinate = new Coordinate(poiLongitude, poiLatitude, poiAltitude);
+            Point p3d = gisUtil.createPointWGS84(coordinate);
+            poiToSave.setGisGeom(p3d);
+        }
+
+        poiToSave = poiBean.storePoi(poiToSave);
+
+        if (poiToSave != null) {
+            return Response.status(Response.Status.CREATED)
+                    .entity(poiToSave)
+                    .build();
+        } else {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+                    .entity("Failed to create PointOfInterest")
+                    .build();
+        }
+    }
+
     /**
      * @param organizationId Id of the organization in question
      * @param poiTypesStr Comma separated list of poiTypes
diff --git a/src/main/java/no/nibio/web/forms/FormValidator.java b/src/main/java/no/nibio/web/forms/FormValidator.java
index 65214d7edbc41e02477cbba2e8a2532528797d02..d6d0ef0fdfeab610818d29eff8987ae1f08ab447 100755
--- a/src/main/java/no/nibio/web/forms/FormValidator.java
+++ b/src/main/java/no/nibio/web/forms/FormValidator.java
@@ -44,6 +44,7 @@ import no.nibio.vips.logic.controller.session.SessionControllerGetter;
 import no.nibio.vips.logic.controller.session.UserBean;
 import no.nibio.vips.logic.i18n.SessionLocaleUtil;
 import org.apache.commons.validator.routines.EmailValidator;
+import org.slf4j.LoggerFactory;
 
 /**
  * Uses form configuration set in JSON files in [WARFILE]/formdefinitions/, or 
@@ -57,6 +58,8 @@ import org.apache.commons.validator.routines.EmailValidator;
  * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
  */
 public class FormValidator {
+
+    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(FormValidator.class);
     
     @EJB
     UserBean userBean;
@@ -340,6 +343,7 @@ public class FormValidator {
                 {
                     field.setValid(false);
                     field.setValidationMessage(resourceBundle.getString("fieldIsRequired"));
+                    LOGGER.debug(field.getName() + " with a value of " + field.getWebValue() + " is considered to be NULL");
                 }
             }
             
diff --git a/src/main/resources/db/migration/V18__UseGridWeatherData.sql b/src/main/resources/db/migration/V18__UseGridWeatherData.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0775bd72877061f15cbcc5f4316a5dec35e79ef9
--- /dev/null
+++ b/src/main/resources/db/migration/V18__UseGridWeatherData.sql
@@ -0,0 +1,3 @@
+-- Adding this property when adding support for gridded weather datasources in VIPS
+ALTER TABLE forecast_configuration
+ADD COLUMN use_grid_weather_data BOOLEAN DEFAULT FALSE;
\ No newline at end of file
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
index 61a9d67be0a612236834e7439c189538ad000d20..76a3c2201f800462755a96c228b09f58d07fffed 100755
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
@@ -426,7 +426,7 @@ locationIsPrivate = Location is private
 
 locationIsPublic = Location is public
 
-locationPointOfInterestId = Location Id
+locationPointOfInterestId = Location
 
 logInterval = Log interval
 
@@ -1056,6 +1056,8 @@ privacyStatement=Privacy statement
 privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf
 thresholdDSVMax=DSV threshold for high infection risk
 thresholdDSVTempMin=Minimum temperature for DSV calculation
+useGridWeatherData=Use grid weather data
+doNotUse=Do not use
 observationTimeSeriesId=Timeseries
 observationTimeSeriesLabel=Timeseries label
 observationId=Observation
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties
index 87647c45dde3c281d5d68f2586191610a2faee80..b632979d4bef2c6f99f1b0a2908e54e7ceee5671 100755
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties
@@ -1046,3 +1046,5 @@ privacyStatement=Privacy statement
 privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf
 thresholdDSVMax=DSV threshold for high infection risk
 thresholdDSVTempMin=Minimum temperature for DSV calculation
+useGridWeatherData=Use grid weather data
+doNotUse=Do not use
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties
index 351048a470187ba55766c9205a30a2a9403a2f17..286433a39e854e88ffbc69e3174ea1a676fa5e55 100755
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties
@@ -1044,3 +1044,5 @@ privacyStatement=Privacy statement
 privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf
 thresholdDSVMax=DSV threshold for high infection risk
 thresholdDSVTempMin=Minimum temperature for DSV calculation
+useGridWeatherData=Use grid weather data
+doNotUse=Do not use
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
index 966906932eae6e2f1e34720a20b75e2c4992b601..9c382a81b2a292d7ff50ce60a05113d37d169b86 100755
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
@@ -419,11 +419,13 @@ listPests = Skadegj\u00f8rerliste
 listSelectedCropCategoryOnTop = List kulturer fra valgt gruppe \u00f8verst
 
 localName = Lokalt navn
+
 location=Sted
 
 locationIsPrivate = Sted skal ikke offentliggj\u00f8res
 
 locationIsPublic = Sted kan vises offentlig
+
 locationPointOfInterestId=Sted-Id
 
 logInterval = M\u00e5leintervall
@@ -1054,6 +1056,8 @@ privacyStatement=Personvernerkl\u00e6ring
 privacyStatementFileName=Personvernerklaering_NIBIO-VIPS.pdf
 thresholdDSVMax=DSV-terskel for h\u00f8y infeksjonsrisiko
 thresholdDSVTempMin=Minimumstemperatur for beregning av DSV
+useGridWeatherData=Bruk v\u00e6rdata fra rutenett
+doNotUse=Ikke bruk
 observationTimeSeriesId=Tidsserie-Id
 observationTimeSeriesLabel=Tidsserie
 observationId=Observasjon-Id
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties
index 1a791041100981ea65f2c5acf16d66132a05823d..044f7b23e28adf500929451368da17dfde0b3908 100755
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties
@@ -1046,3 +1046,5 @@ privacyStatement=Privacy statement
 privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf
 thresholdDSVMax=DSV threshold for high infection risk
 thresholdDSVTempMin=Minimum temperature for DSV calculation
+useGridWeatherData=Use grid weather data
+doNotUse=Do not use
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties
index e118325c6d1829c0f06ba616f7f1a19c540cce12..85ad53c6c5179ff6f18fc03b386acc6ffc229acf 100755
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties
@@ -1043,3 +1043,5 @@ thresholdDSVTempMin=Minimum temperature for DSV calculation
 isBroadcast=Is broadcast
 yes=Yes
 no=No
+useGridWeatherData=Use grid weather data
+doNotUse=Do not use
diff --git a/src/main/webapp/css/mapModal.css b/src/main/webapp/css/mapModal.css
new file mode 100644
index 0000000000000000000000000000000000000000..dd9694b061060a316023b1f9ae9faabe4f3695be
--- /dev/null
+++ b/src/main/webapp/css/mapModal.css
@@ -0,0 +1,84 @@
+.modal {
+    display: none;
+    position: fixed;
+    z-index: 1000;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    background-color: rgba(0, 0, 0, 0.9);
+}
+
+.modal-content {
+    position: relative;
+    height: 100%;
+    width: 100%;
+    background-color: #fefefe;
+}
+
+#open-map-modal-icon {
+    font-size: 30px;
+    margin-left: 15px;
+    cursor: pointer;
+}
+
+#selectedPointInfo {
+    font-family: Arial, sans-serif;
+    font-size: 12px;
+    box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
+    width: 150px;
+    position: absolute;
+    bottom: 20px;
+    left: 20px;
+    background: white;
+    padding: 10px;
+    border-radius: 5px;
+    z-index: 1100;
+}
+
+#zoomToMyLocationButton {
+    position: absolute;
+    top: 10px;
+    left: 50px;
+    z-index: 1100;
+}
+
+#confirmButton {
+    margin-top: 10px;
+}
+
+.leaflet-container {
+    cursor: default !important;  /* Force the arrow cursor */
+}
+
+.leaflet-container {
+    font-size: 0.9rem; /* Make font-size of popup larger */
+}
+
+.close-button {
+    position: absolute;
+    top: 10px;
+    right: 15px;
+    font-size: 50px;
+    font-weight: bold;
+    text-shadow:
+            -1px -1px 0 #000,  /* Top left shadow */
+            1px -1px 0 #000,   /* Top right shadow */
+            -1px 1px 0 #000,   /* Bottom left shadow */
+            1px 1px 0 #000;
+    color: white;
+    background-color: transparent;
+    border: none;
+    cursor: pointer;
+    z-index: 1500;
+}
+
+#newPointForm {
+    z-index: 1200;
+    position: absolute;
+}
+
+#poiPopup {
+    width: 100px;
+}
\ No newline at end of file
diff --git a/src/main/webapp/formdefinitions/forecastConfigurationForm.json b/src/main/webapp/formdefinitions/forecastConfigurationForm.json
index 95c43c592f962e33cfe7900f5c63fd43a3058314..ca6fd66a4cf07d58acbed4e9a62d86c9c23b3769 100755
--- a/src/main/webapp/formdefinitions/forecastConfigurationForm.json
+++ b/src/main/webapp/formdefinitions/forecastConfigurationForm.json
@@ -57,6 +57,11 @@
             "dataType" : "STRING",
             "required" : false
         },
+        {
+            "name" : "useGridWeatherData",
+            "dataType" : "STRING",
+            "required" : false
+        },
         {
             "name" : "locationPointOfInterestId",
             "dataType" : "INTEGER",
diff --git a/src/main/webapp/js/mapModal.js b/src/main/webapp/js/mapModal.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e522eacf17b7b393b943accd3c2f79cc89755b7
--- /dev/null
+++ b/src/main/webapp/js/mapModal.js
@@ -0,0 +1,439 @@
+// mapModal.js
+import {
+    map,
+    tileLayer,
+    geoJSON,
+    circleMarker,
+    marker,
+    GeoJSON,
+    DomEvent
+} from 'https://unpkg.com/leaflet/dist/leaflet-src.esm.js';
+
+/**
+ * Uses css classes from bootstrap 3.4.1
+ *
+ */
+class MapModal {
+
+    /**
+     * @param mapContainerId    The id of the HTML element to which the map should be added
+     * @param typeNameMap   A mapping from pointOfInterestTypeIds to their localized names
+     * @param geoJsonData   GeoJson containing all features which should be displayed on the map
+     * @param allowNewPoints    Whether or not the user should be allowed to add new points
+     * @param callbackOnPersistNew  Callback function for persisting newly created point
+     * @param callbackOnClose   Callback function to call when closing the modal
+     */
+    constructor(mapContainerId, typeNameMap, geoJsonData, allowNewPoints = false, callbackOnPersistNew = null, callbackOnClose = null) {
+        this.mapContainerId = mapContainerId;
+        this.typeNameMap = typeNameMap;
+        this.geoJsonData = geoJsonData;
+        this.allowNewPoints = allowNewPoints;
+        this.callbackOnPersistNew = callbackOnPersistNew;
+        this.callbackOnClose = callbackOnClose;
+
+        this.map = null;
+        this.isMapInitialized = false;
+        this.selectedPointLayer = null;
+        this.createdPointLayer = null;
+        this.createdPoints = [];
+
+        // Colours for the available types of pois
+        this.typeColorMap = {
+            0: "#5DADE2",  // Bright Blue
+            1: "#58D68D",  // Vibrant Green
+            2: "#AF7AC5",  // Medium Lavender
+            3: "#F5B041",  // Warm Orange
+            5: "#F7DC6F",  // Bright Yellow
+            6: "#DC7633",   // Rich Brown
+            7: "#FF33A6"  // Vivid Magenta
+        };
+
+    }
+
+    initMap() {
+        if (!this.isMapInitialized) {
+            // Initialize the map centered on Norway
+            this.map = map(this.mapContainerId).setView([63.4226, 10.3951], 5);
+            tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+                maxZoom: 19
+            }).addTo(this.map);
+
+            this.setUpSelectedPointInfoPanel();
+            this.setUpZoomToCurrentLocation();
+
+            // Add points to the map
+            geoJSON(this.geoJsonData, {
+                filter: (feature) => feature.geometry.type === "Point",
+                pointToLayer: (feature, latlng) => {
+                    const color = this.typeColorMap[feature.properties.pointOfInterestTypeId] || "#3498DB";
+                    return circleMarker(latlng, {
+                        radius: 8,  // Size of the marker
+                        fillColor: color,
+                        color: "#FFFFFF",  // Border color
+                        weight: 2,  // Border thickness
+                        opacity: 1,
+                        fillOpacity: 0.8
+                    });
+                },
+                onEachFeature: (feature, layer) => {
+                    layer.bindPopup(this.popupContent(feature));
+                    layer.on('click', () => this.selectPoint(feature, layer));
+                }
+            }).addTo(this.map);
+
+            // Enable adding new points if allowed
+            if (this.allowNewPoints) {
+                console.info("Enable point creation")
+                this.enablePointCreation();
+            }
+            this.isMapInitialized = true;
+        }
+    }
+
+    /**
+     * Create information panel for selected point, initially hidden.
+     */
+    setUpSelectedPointInfoPanel() {
+        const selectedPointInfoHtml = `
+        <div id="selectedPointInfo" style="display: none;">
+            <div id="infoMessage"></div>
+            <button id="confirmButton" class="btn btn-primary">Velg sted</button>
+        </div>`;
+        document.getElementById(this.mapContainerId).insertAdjacentHTML('beforeend', selectedPointInfoHtml);
+    }
+
+    setUpZoomToCurrentLocation() {
+        const zoomButtonHtml = `<button id="zoomToMyLocationButton" class="btn btn-primary">Zoom til meg</button>`;
+        document.getElementById(this.mapContainerId).insertAdjacentHTML('beforeend', zoomButtonHtml);
+
+        let zoomButton = document.getElementById('zoomToMyLocationButton')
+        DomEvent.disableClickPropagation(zoomButton);
+        zoomButton.addEventListener('click', () => {
+            if (navigator.geolocation) {
+                navigator.geolocation.getCurrentPosition((position) => {
+                    const latitude = position.coords.latitude;
+                    const longitude = position.coords.longitude;
+
+                    this.map.setView([latitude, longitude], 13);
+
+                    // Add a marker at the user's location
+                    const userLocationMarker = marker([latitude, longitude]).addTo(this.map);
+                }, (error) => {
+                    console.error('Geolocation failed: ' + error.message);
+                    alert('Unable to retrieve your location.');
+                });
+            } else {
+                alert('Geolocation is not supported by this browser.');
+            }
+        });
+    }
+
+    /**
+     * Display the panel which contains information about the currently selected point,
+     * and a button to bring you back to the original page.
+     */
+    displaySelectedPointInfo(feature) {
+        const name = feature.properties.pointOfInterestName;
+        console.info("Display selected point " + name);
+        const type = this.typeNameMap[feature.properties.pointOfInterestTypeId];
+        const coordinates = feature.geometry.coordinates[1].toFixed(5) + ", " + feature.geometry.coordinates[0].toFixed(5);
+
+        const selectedPointInfo = document.getElementById('selectedPointInfo');
+        const infoMessage = document.getElementById('infoMessage');
+
+        infoMessage.innerHTML = `<strong>${name}</strong><br>${coordinates}<br>${type}`;
+        selectedPointInfo.style.display = 'block';
+
+        const confirmButton = document.getElementById('confirmButton');
+        confirmButton.style.display = 'block';
+        confirmButton.onclick = () => {
+            this.confirmSelection(feature);
+        };
+    }
+
+    selectPointById(pointOfInterestId) {
+        const selectedFeature = this.getFeatureById(pointOfInterestId);
+        const selectedLayer = this.getLayerById(pointOfInterestId);
+        this.selectPoint(selectedFeature, selectedLayer, true);
+    }
+
+    getFeatureById(pointOfInterestId) {
+        return this.geoJsonData.features.find(feature => feature.properties.pointOfInterestId == pointOfInterestId);
+    }
+
+    getLayerById(pointOfInterestId) {
+        let result = null;
+        this.map.eachLayer(layer => {
+            if (layer instanceof GeoJSON) {
+                layer.eachLayer(l => {
+                    if (l.feature && l.feature.properties.pointOfInterestId == pointOfInterestId) {
+                        result = l;
+                    }
+                });
+            }
+        });
+        return result;
+    }
+
+    selectPoint(feature, layer, zoomInToSelected = false) {
+        console.info("Select point", feature);
+        // Deselect previously selected point, if any
+        if (this.selectedPointLayer) {
+            const color = this.typeColorMap[feature.properties.pointOfInterestTypeId] || "#3498DB";
+            this.selectedPointLayer.setStyle({
+                radius: 8,  // Size of the marker
+                fillColor: color,
+                color: "#FFFFFF",
+                weight: 2,
+                opacity: 1,
+                fillOpacity: 0.8
+            });
+        }
+
+        // Highlight the new selected point
+        layer.setStyle({
+            radius: 12,
+            fillColor: "#FF5733",
+            fillOpacity: 1,
+        });
+
+        if (zoomInToSelected) {
+            const latLng = layer.getLatLng();
+            this.map.setView(latLng, 10);
+        }
+
+        this.displaySelectedPointInfo(feature)
+        this.selectedPointLayer = layer;
+    }
+
+    confirmSelection(feature) {
+        console.info("Confirm selection", feature);
+        let poiId = feature.properties.pointOfInterestId;
+        if (!poiId && typeof this.callbackOnPersistNew === 'function') {
+            const pointData = {
+                name: feature.properties.pointOfInterestName,
+                typeId: feature.properties.pointOfInterestTypeId,
+                longitude: feature.geometry.coordinates[0],
+                latitude: feature.geometry.coordinates[1]
+            };
+            console.info("Persist new", pointData);
+            this.callbackOnPersistNew(pointData);
+        }
+        if (typeof this.callbackOnClose === 'function') {
+            this.callbackOnClose(poiId);
+            console.info("Goodbye from map modal!")
+        }
+        this.closeModal();
+    }
+
+    enablePointCreation() {
+        this.map.on('click', (e) => {
+            const latlng = e.latlng;
+
+            // If a form already exists, remove it
+            this.closeNewPointFormIfOpen();
+
+            // Calculate the pixel position from the map's click event
+            const containerPoint = this.map.latLngToContainerPoint(latlng);
+            const newPointFormElement = this.addHtmlElementNewPointForm(containerPoint.x, containerPoint.y, latlng.lat, latlng.lng)
+
+            // Click inside the form should not propagate to underlying map
+            DomEvent.disableClickPropagation(newPointFormElement);
+            // Add event listener to close the form if clicked outside
+            document.addEventListener('click', this.handleClickOutsidePointForm.bind(this), true);
+
+            const closeButton = newPointFormElement.querySelector("#close-button");
+            const nameInput = newPointFormElement.querySelector('#name');
+            const latitudeInput = newPointFormElement.querySelector('#latitude');
+            const longitudeInput = newPointFormElement.querySelector('#longitude');
+            const typeInput = newPointFormElement.querySelector('#type');
+            const submitButton = newPointFormElement.querySelector('#submit-button');
+
+            const validateInputs = () => {
+                const isValidLat = !isNaN(parseFloat(latitudeInput.value)) && isFinite(latitudeInput.value);
+                const isValidLng = !isNaN(parseFloat(longitudeInput.value)) && isFinite(longitudeInput.value);
+                submitButton.disabled = !(isValidLat && isValidLng);
+            };
+            latitudeInput.addEventListener('input', validateInputs);
+            longitudeInput.addEventListener('input', validateInputs);
+            validateInputs();
+
+            closeButton.addEventListener('click', function() {
+                newPointFormElement.remove();
+            });
+
+            submitButton.addEventListener('click', () => {
+                this.removeExistingNewPoint();
+                this.setNewPointAsSelected(nameInput.value, parseFloat(latitudeInput.value), parseFloat(longitudeInput.value), parseInt(typeInput.value, 10));
+                newPointFormElement.remove();
+            });
+        });
+    }
+
+    handleClickOutsidePointForm(event) {
+        const formElement = document.getElementById('newPointForm');
+
+        // If the clicked element is not inside the form, close the form
+        if (formElement && !formElement.contains(event.target)) {
+            this.closeNewPointFormIfOpen();
+        }
+    }
+
+    closeNewPointFormIfOpen() {
+        const formElement = document.getElementById('newPointForm');
+        if (formElement) {
+            formElement.remove();
+        }
+
+        // Remove the event listener after closing the form
+        document.removeEventListener('click', this.handleClickOutsidePointForm.bind(this), true);
+    }
+
+    createFeatureForPoint(longitude, latitude, name, type) {
+        console.info("Create feature for [" + longitude + "," + latitude + "," + name + "," + type + "]");
+        return {
+            "type": "Feature",
+            "geometry": {
+                "type": "Point",
+                "coordinates": [longitude, latitude]
+            },
+            "properties": {
+                "pointOfInterestName": name,
+                "pointOfInterestTypeId": type
+            }
+        };
+    }
+
+    addNewPointToMap(point) {
+        console.info("Add new point to map", point);
+        return geoJSON(point, {
+            pointToLayer: (feature, latlng) => {
+                return circleMarker(latlng, {
+                    radius: 8,
+                    fillColor: 'green',
+                    color: '#000',
+                    weight: 1,
+                    opacity: 1,
+                    fillOpacity: 0.8
+                });
+            },
+            onEachFeature: (feature, layer) => {
+                layer.bindPopup(this.popupContent(feature));
+                layer.on('click', () => this.selectPoint(feature, layer));
+            }
+        }).addTo(this.map);
+    }
+
+    setNewPointAsSelected(name, latitude, longitude, type) {
+        const feature = this.createFeatureForPoint(longitude, latitude, name, type);
+        this.createdPoints.push(feature);
+        this.createdPointLayer = this.addNewPointToMap(feature);
+        this.createdPointLayer.eachLayer((layer) => {
+            this.selectPoint(feature, layer);
+        });
+    }
+
+    removeExistingNewPoint(){
+        if (this.createdPointLayer) {
+            this.map.removeLayer(this.createdPointLayer);
+        }
+        if (this.createdPoints.length > 0) {
+            this.createdPoints.pop();
+        }
+    }
+
+    popupContent(feature) {
+        const localizedTypeName = this.typeNameMap[feature.properties.pointOfInterestTypeId];
+        const coordinates = feature.geometry.coordinates[1].toFixed(5) + ", " + feature.geometry.coordinates[0].toFixed(5);
+        return `<div id="poiPopup">
+                <strong>${feature.properties.pointOfInterestName}</strong><br>${coordinates}<br>${localizedTypeName}
+                </div>`;
+    }
+
+    /**
+     * Creates the HTML form for adding a new point, and add it to the map container.
+     *
+     * @param positionX Where to place the form on the x axis
+     * @param positionY Where to place the form on the y axis
+     * @param latitude  Latitude of the point clicked
+     * @param longitude Longitude of the point clicked
+     * @returns {Element}
+     */
+    addHtmlElementNewPointForm(positionX, positionY, latitude, longitude) {
+        const html = `
+            <div id="newPointForm" class="panel panel-default"  style="top: ${positionY}px; left: ${positionX}px;">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Opprett nytt sted</h3>
+                    <span id="close-button" style="position: absolute; top: 5px; right: 10px; cursor: pointer; font-size: 18px;">&times;</span>
+                </div>
+                <div class="panel-body">
+                    <div class="form-group">
+                        <label for="name">Navn:</label>
+                        <input type="text" class="form-control" id="name" name="name">
+                    </div>
+                    <div class="form-group">
+                        <label for="latitude">Breddegrad:</label>
+                        <input type="text" class="form-control" id="latitude" name="latitude" value="${latitude}">
+                    </div>
+                    <div class="form-group">
+                        <label for="longitude">Lengdegrad:</label>
+                        <input type="text" class="form-control" id="longitude" name="longitude" value="${longitude}">
+                    </div>
+                    <div class="form-group">
+                        <label for="poiTypeSelect">Type:</label>
+                        <select class="form-control" id="type" name="type">
+                            <option value="2">${this.typeNameMap[2]}</option>
+                            <option value="3">${this.typeNameMap[3]}</option>
+                            <option value="5">${this.typeNameMap[5]}</option>
+                        </select>
+                    </div>
+                    <div class="form-group text-right">
+                        <button id="submit-button" class="btn btn-primary">OK</button>
+                    </div>                
+                </div>
+            </div>`;
+        const tmpContainer = document.createElement("div");
+        tmpContainer.innerHTML = html;
+        const htmlElement = tmpContainer.querySelector('#newPointForm');
+        document.getElementById(this.mapContainerId).appendChild(htmlElement);
+        return htmlElement;
+    }
+
+    /**
+     * Function is called when newly created point of interest is successfully persisted to database.
+     * @param pointOfInterestId
+     */
+    saveSuccess(pointOfInterestId) {
+        if (this.createdPoints.length < 1 || !this.createdPointLayer) {
+            console.error('No newly created points, unable to update with pointOfInterestId=' + pointOfInterestId);
+            return;
+        }
+        let latestCreatedFeature = this.createdPoints[0];
+        latestCreatedFeature.properties.pointOfInterestId = pointOfInterestId;
+        this.geoJsonData.features.push(latestCreatedFeature);
+        this.createdPoints = [];
+
+        this.map.removeLayer(this.createdPointLayer);
+        this.addNewPointToMap(latestCreatedFeature);
+        this.createdPointLayer = null;
+
+        console.info("this.createdPoints", this.createdPoints);
+        console.info("this.createdPointLayer", this.createdPointLayer);
+        console.info("this.geoJsonData.features", this.geoJsonData.features);
+    }
+
+    openModal(selectedPointOfInterestId) {
+        if(selectedPointOfInterestId) {
+            this.selectPointById(selectedPointOfInterestId);
+        }
+        document.getElementById('mapModal').style.display = 'block';
+        this.initMap();
+    }
+
+    closeModal() {
+        document.getElementById('mapModal').style.display = 'none';
+    }
+}
+
+// Export the module
+export default MapModal;
\ No newline at end of file
diff --git a/src/main/webapp/js/validateForm.js b/src/main/webapp/js/validateForm.js
index 0e4a8e2199f33cc8708ad12f040b266889a1dd31..833dfe22f7c1995643b4943c6cac1863557ec37f 100755
--- a/src/main/webapp/js/validateForm.js
+++ b/src/main/webapp/js/validateForm.js
@@ -268,7 +268,8 @@ function validateFieldActual(fieldEl, theForm, formDefinitionKey)
     // Single select field - check for nullValue
     if(fieldDefinition.fieldType === fieldTypes.TYPE_SELECT_SINGLE)
     {
-        webValue = fieldEl.options[fieldEl.selectedIndex].value;
+        // Fallback if this is not a select list (could be a readonly list using a twin hidden field)
+        webValue = webValue = fieldEl.options != undefined ? fieldEl.options[fieldEl.selectedIndex].value : fieldEl.value;
         if(fieldDefinition.nullValue === webValue && fieldDefinition.required === true)
         {
             invalidizeField(fieldEl, theForm, getI18nMsg("fieldIsRequired",null));
@@ -471,7 +472,7 @@ function validateFieldActual(fieldEl, theForm, formDefinitionKey)
 }
 
 /**
- * Recursive function to travers upwards in tree until we find the form
+ * Recursive function to traverse upwards in tree until we find the form
  * for the given element
  * @param {DOMElement} fieldEl
  * @returns {DOMelement} the form
diff --git a/src/main/webapp/templates/forecastConfigurationForm.ftl b/src/main/webapp/templates/forecastConfigurationForm.ftl
index c4656b5809c21741a48ddec2aac42bd9940a25c8..d1037a361da27fa37e7d337816b9d9f56b4ce0bd 100755
--- a/src/main/webapp/templates/forecastConfigurationForm.ftl
+++ b/src/main/webapp/templates/forecastConfigurationForm.ftl
@@ -1,6 +1,6 @@
-<#-- 
-    Copyright (c) 2016 NIBIO <http://www.nibio.no/>. 
-  
+<#--
+    Copyright (c) 2016 NIBIO <http://www.nibio.no/>.
+
   This file is part of VIPSLogic.
   This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -16,21 +16,108 @@
     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 --><#include "master.ftl">
 <#macro page_head>
-        <title>${i18nBundle.viewForecastConfiguration}</title>
+    <title>${i18nBundle.viewForecastConfiguration}</title>
 </#macro>
 <#macro custom_js>
-	<script src="/js/resourcebundle.js"></script>
-	<script src="/js/forecastConfigurationForm.js"></script>
-	<script src="/js/validateForm.js"></script>
-	<script src="//code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
-	<link href="//code.jquery.com/ui/1.10.3/themes/redmond/jquery-ui.css" rel="stylesheet" />
-	<script type="text/javascript" src="/js/3rdparty/modernizr_custom.js"></script>
-	<script type="text/javascript" src="/js/3rdparty/moment.min.js"></script>
+    <script src="/js/resourcebundle.js"></script>
+    <script src="/js/forecastConfigurationForm.js"></script>
+    <script src="/js/validateForm.js"></script>
+    <script src="//code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
+    <link href="//code.jquery.com/ui/1.10.3/themes/redmond/jquery-ui.css" rel="stylesheet" />
+    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
+    <link rel="stylesheet" href="/css/mapModal.css" />
+    <script type="text/javascript" src="/js/3rdparty/modernizr_custom.js"></script>
+    <script type="text/javascript" src="/js/3rdparty/moment.min.js"></script>
     <script type="text/javascript" src="/js/environment.js"></script>
     <script type="text/javascript" src="/js/util.js"></script>
     <script type="text/javascript" src="/js/3rdparty/chosen.jquery.min.js"></script>
     <script type="text/javascript">
         $(".chosen-select").chosen();
+    </script>
+    <script type="module">
+        import MapModal from '/js/mapModal.js';
+        function callbackPersistNewPoint(pointData) {
+            const userId = ${user.userId};
+            const params = {
+                'name': pointData.name,
+                'typeId': pointData.typeId,
+                'longitude': pointData.longitude,
+                'latitude': pointData.latitude,
+                'altitude': '0', // default value - populate using a service for getting altitude for coordinates?
+                'userId': userId,
+            }
+            $.ajax({
+                url: "/rest/poi",
+                type: "POST",
+                contentType: "application/json",
+                data: JSON.stringify(params),
+                success: function(response) {
+                    addOption(response.pointOfInterestId, response.name);
+                    mapModalInstance.saveSuccess(response.pointOfInterestId);
+                    console.info("Success:", response);
+                },
+                error: function(jqXHR, textStatus, errorThrown) {
+                    console.error("Error:", textStatus, errorThrown);
+                }
+            });
+        }
+        function callbackUpdateLocationPointOfInterest(pointOfInterestId) {
+            const selectBox = document.querySelector('select[name="locationPointOfInterestId"]');
+            if(pointOfInterestId) {
+                let optionFound = false;
+                for (let i = 0; i < selectBox.options.length; i++) {
+                    if (selectBox.options[i].value == pointOfInterestId) {
+                        selectBox.selectedIndex = i; // Select the matching option
+                        optionFound = true;
+                        break;
+                    }
+                }
+                if (!optionFound) {
+                    console.error("No matching option found for poi.id:", pointOfInterestId);
+                }
+            }
+        }
+        // TODO Ensure options are sorted alphabetically..?
+        function addOption(pointOfInterestId, name) {
+            let selectElement = document.querySelector('select[name="locationPointOfInterestId"]');
+            let newOption = document.createElement("option");
+            newOption.value = pointOfInterestId;
+            newOption.text = name;
+            selectElement.insertBefore(newOption, selectElement.firstChild);
+            selectElement.value = pointOfInterestId;
+        }
+        const typeNameMap = {
+            0: "${i18nBundle["pointOfInterestType_0"]}",
+            1: "${i18nBundle["pointOfInterestType_1"]}",
+            2: "${i18nBundle["pointOfInterestType_2"]}",
+            3: "${i18nBundle["pointOfInterestType_3"]}",
+            5: "${i18nBundle["pointOfInterestType_5"]}",
+            6: "${i18nBundle["pointOfInterestType_6"]}",
+            7: "${i18nBundle["pointOfInterestType_7"]}"
+        };
+
+        const poiGeoJson = JSON.parse('${locationPointOfInterestsGeoJson?json_string}');
+        const stationGeoJson = JSON.parse('${weatherStationPointOfInterestsGeoJson?json_string}')
+        const mapModalInstance = new MapModal('mapContainer', typeNameMap, poiGeoJson, true, callbackPersistNewPoint, callbackUpdateLocationPointOfInterest);
+        window.mapModalInstance = mapModalInstance;
+
+        // If poi is selected, send id to map modal before opening
+        window.openModal = () => {
+            const selectElement = document.querySelector('select[name="locationPointOfInterestId"]');
+            const selectedOption = selectElement.options[selectElement.selectedIndex];
+
+            let selectedPointOfInterestId;
+            const value = selectedOption.value;
+            if(value) {
+                const parsedValue = parseInt(value, 10);
+                if (!isNaN(parsedValue) && parsedValue > 0) {
+                    selectedPointOfInterestId = parsedValue;
+                }
+            }
+            window.mapModalInstance.openModal(selectedPointOfInterestId);
+        };
+
+        window.closeModal = () => window.mapModalInstance && window.mapModalInstance.closeModal();
     </script>
 	<script type="text/javascript">
             $(document).ready(function() {
@@ -190,6 +277,47 @@
                             }
                     }
             };
+          var handleUseGridWeatherDataClicked = function(theCheckBox, weatherStationPointOfInterestId) {
+            weatherStationList = document.getElementById("weatherStationPointOfInterestId");
+            weatherStationPointOfInterestIdHiddenField = document.getElementById("weatherStationPointOfInterestIdHidden");
+            if(theCheckBox.checked)
+            {
+              // Select weatherStationId -2
+              weatherStationList.selectedIndex = 0;
+              // Disable the weatherstation select list
+              weatherStationList.disabled=true;
+              weatherStationList.name="weatherStationPointOfInterestIdDisabled";
+
+              // Enable the hidden field
+              weatherStationPointOfInterestIdHiddenField.disabled=false
+              weatherStationPointOfInterestIdHiddenField.name="weatherStationPointOfInterestId";
+            }
+            else
+            {
+              // Select weatherStationId -1 OR the optionally provided weatherStationPointOfInterestId
+              if(weatherStationPointOfInterestId == undefined || weatherStationPointOfInterestId == null)
+              {
+                weatherStationList.selectedIndex = 1;
+              }
+              else
+              {
+                 for(let i=0;i<weatherStationList.options.length;i++)
+                 {
+                    weatherStationList.options[i].selected = parseInt(weatherStationList.options[i].value) == weatherStationPointOfInterestId;
+                 }
+              }
+              // Enable the weather station select list
+              weatherStationList.disabled=false;
+              weatherStationList.name="weatherStationPointOfInterestId";
+              // Disable the hidden field
+              weatherStationPointOfInterestIdHiddenField.disabled=true
+              weatherStationPointOfInterestIdHiddenField.name="weatherStationPointOfInterestIdDisabled";
+            }
+          };
+
+          // Setting weather station select list state correct on page load
+          handleUseGridWeatherDataClicked(document.getElementById("useGridWeatherData")<#if forecastConfiguration.weatherStationPointOfInterestId?has_content>,${forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId}</#if>);
+
 	</script>
 </#macro>
 <#macro custom_css>
@@ -253,6 +381,7 @@
 				<#if ! user.isSuperUser() && ! user.isOrganizationAdmin()> readonly="readonly" disabled="disabled"</#if>/>
 			</label>
 			${i18nBundle.isPrivate}
+      <span class="help-block" id="${formId}_isPrivate_validation"></span>
 		</div>
 	  </div>
 	  <div class="form-group">
@@ -272,24 +401,43 @@
 	  <#if !multipleNew?has_content || !multipleNew>
 	  <div class="form-group">
 	    <label for="locationPointOfInterestId">${i18nBundle.locationPointOfInterestId}</label>
-	    <select class="form-control" name="locationPointOfInterestId" onblur="validateField(this);">
-	    	<option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.locationPointOfInterestId?lower_case}</option>
-	    	<#list locationPointOfInterests?sort_by("name") as poi>
-	    	<option value="${poi.pointOfInterestId}"<#if forecastConfiguration.locationPointOfInterestId?has_content && poi.pointOfInterestId == forecastConfiguration.locationPointOfInterestId.pointOfInterestId> selected="selected"</#if>>${poi.name}</option>
-	    	</#list>
-	    </select>
+          <div class="select-container" style="flex: 1; display: flex; align-items: center;">
+            <select class="form-control" id="locationPointOfInterestId" name="locationPointOfInterestId" onblur="validateField(this);" style="width: calc(100% - 30px);">
+                <option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.locationPointOfInterestId?lower_case}</option>
+                <#list locationPointOfInterests?sort_by("name") as poi>
+                <option value="${poi.pointOfInterestId}"<#if forecastConfiguration.locationPointOfInterestId?has_content && poi.pointOfInterestId == forecastConfiguration.locationPointOfInterestId.pointOfInterestId> selected="selected"</#if>>${poi.name}</option>
+                </#list>
+            </select>
+            <i id="open-map-modal-icon" class="fa fa-map-marker" onclick="openModal()"></i>
+          </div>
+          <div id="mapModal" class="modal">
+              <div class="modal-content">
+                  <span class="close-button" onclick="closeModal()">&times;</span>
+                  <div id="mapContainer" style="height: 100vh; width: 100%; position: relative;"></div>
+              </div>
+          </div>
 	    <span class="help-block" id="${formId}_locationPointOfInterestId_validation"></span>
 	  </div>
 	  <div class="form-group">
 	    <label for="weatherStationPointOfInterestId">${i18nBundle.weatherStationPointOfInterestId}</label>
-	    <select class="form-control" name="weatherStationPointOfInterestId" onblur="validateField(this);">
-	    	<option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.weatherStationPointOfInterestId?lower_case}</option>
+	    <select class="form-control" id="weatherStationPointOfInterestId" name="weatherStationPointOfInterestId" onblur="if(!document.getElementById('useGridWeatherData').checked) {validateField(this);};">
+	    	<option value="-2">${i18nBundle.doNotUse} ${i18nBundle.weatherStationPointOfInterestId?lower_case}</option>
+        <option value="-1"<#if !forecastConfiguration.weatherStationPointOfInterestId?has_content && !forecastConfiguration.useGridWeatherData> selected="selected"</#if>>${i18nBundle.pleaseSelect} ${i18nBundle.weatherStationPointOfInterestId?lower_case}</option>
 	    	<#list weatherStationPointOfInterests?sort_by("name") as poi>
 	    	<option value="${poi.pointOfInterestId}"<#if forecastConfiguration.weatherStationPointOfInterestId?has_content && poi.pointOfInterestId == forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId> selected="selected"</#if>>${poi.name}</option>
 	    	</#list>
 	    </select>
 	    <span class="help-block" id="${formId}_weatherStationPointOfInterestId_validation"></span>
 	  </div>
+    <input type="hidden" id="weatherStationPointOfInterestIdHidden" name="weatherStationPointOfInterestIdDisabled" value="-2" disabled="disabled"/>
+    <div class="form-group">
+		  <div class="checkbox">
+			<label>
+				<input type="checkbox" id="useGridWeatherData" name="useGridWeatherData"<#if forecastConfiguration.useGridWeatherData?has_content && forecastConfiguration.useGridWeatherData == true> checked="checked"</#if> onclick="handleUseGridWeatherDataClicked(this);"/>
+			</label>
+			${i18nBundle.useGridWeatherData}
+      <span class="help-block" id="${formId}_useGridWeatherData_validation"></span>
+		</div>
 	  <#else>
 	  <input type="hidden" name="multipleNew" value="true"/>
 	  <div class="form-group">