diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/model/geojson/GeometryCollection.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/model/geojson/GeometryCollection.java
index 903ac08186b4dc12eb02b0e4792e5f43c3a934df..c23ae1646ad3d1b3ec0080e1209432eb2cb364e4 100644
--- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/model/geojson/GeometryCollection.java
+++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/model/geojson/GeometryCollection.java
@@ -14,13 +14,17 @@
 
 package org.opengroup.osdu.indexer.model.geojson;
 
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import lombok.Data;
+import org.opengroup.osdu.indexer.model.geojson.jackson.FeatureCollectionSerializer;
+import org.opengroup.osdu.indexer.model.geojson.jackson.GeometryCollectionSerializer;
 
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
 @Data
+@JsonSerialize(using = GeometryCollectionSerializer.class)
 public class GeometryCollection extends GeoJsonObject implements Iterable<GeoJsonObject> {
 
     private List<GeoJsonObject> geometries = new ArrayList<GeoJsonObject>();
diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/model/geojson/jackson/GeometryCollectionSerializer.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/model/geojson/jackson/GeometryCollectionSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..e836869ee846d2d0150ea3bfc99f37399017c759
--- /dev/null
+++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/model/geojson/jackson/GeometryCollectionSerializer.java
@@ -0,0 +1,66 @@
+package org.opengroup.osdu.indexer.model.geojson.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import org.opengroup.osdu.indexer.model.geojson.*;
+
+import java.io.IOException;
+
+public class GeometryCollectionSerializer extends JsonSerializer<GeometryCollection> {
+    @Override
+    public void serialize(GeometryCollection value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
+        jsonGenerator.writeStartObject();
+        jsonGenerator.writeStringField("type", "geometrycollection");
+
+        jsonGenerator.writeArrayFieldStart("geometries");
+        for (GeoJsonObject shape : value.getGeometries()) {
+            serializeGeoShape(shape, jsonGenerator);
+        }
+        jsonGenerator.writeEndArray();
+        jsonGenerator.writeEndObject();
+    }
+
+    @Override
+    public void serializeWithType(GeometryCollection value, JsonGenerator jsonGenerator, SerializerProvider provider, TypeSerializer typeSerializer)
+            throws IOException, JsonProcessingException {
+
+        serialize(value, jsonGenerator, provider);
+    }
+
+    private void serializeGeoShape(GeoJsonObject geoJsonObject, JsonGenerator jsonGenerator) throws IOException {
+        if (geoJsonObject instanceof Point) {
+            jsonGenerator.writeStartObject();
+            jsonGenerator.writeStringField("type", "point");
+            jsonGenerator.writeObjectField("coordinates", ((Point) geoJsonObject).getCoordinates());
+            jsonGenerator.writeEndObject();
+        } else if (geoJsonObject instanceof LineString) {
+            jsonGenerator.writeStartObject();
+            jsonGenerator.writeStringField("type", "linestring");
+            jsonGenerator.writeObjectField("coordinates", ((LineString) geoJsonObject).getCoordinates());
+            jsonGenerator.writeEndObject();
+        } else if (geoJsonObject instanceof Polygon) {
+            jsonGenerator.writeStartObject();
+            jsonGenerator.writeStringField("type", "polygon");
+            jsonGenerator.writeObjectField("coordinates", ((Polygon) geoJsonObject).getCoordinates());
+            jsonGenerator.writeEndObject();
+        } else if (geoJsonObject instanceof MultiPoint) {
+            jsonGenerator.writeStartObject();
+            jsonGenerator.writeStringField("type", "multipoint");
+            jsonGenerator.writeObjectField("coordinates", ((MultiPoint) geoJsonObject).getCoordinates());
+            jsonGenerator.writeEndObject();
+        } else if (geoJsonObject instanceof MultiLineString) {
+            jsonGenerator.writeStartObject();
+            jsonGenerator.writeStringField("type", "multilinestring");
+            jsonGenerator.writeObjectField("coordinates", ((MultiLineString) geoJsonObject).getCoordinates());
+            jsonGenerator.writeEndObject();
+        } else if (geoJsonObject instanceof MultiPolygon) {
+            jsonGenerator.writeStartObject();
+            jsonGenerator.writeStringField("type", "multipolygon");
+            jsonGenerator.writeObjectField("coordinates", ((MultiPolygon) geoJsonObject).getCoordinates());
+            jsonGenerator.writeEndObject();
+        }
+    }
+}
diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/schema/converter/SchemaToStorageFormatImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/schema/converter/SchemaToStorageFormatImpl.java
index 69e26c1845455b43584ebed256fae1067c8862a5..768c7d346acdf3eccb6d4fe55b94cb4900f1a1d3 100644
--- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/schema/converter/SchemaToStorageFormatImpl.java
+++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/schema/converter/SchemaToStorageFormatImpl.java
@@ -149,6 +149,7 @@ public class SchemaToStorageFormatImpl implements SchemaToStorageFormat {
         if (virtualProperties == null || virtualProperties.isEmpty())
             return;
 
+        boolean hasVirtualDefaultLocation = false;
         for (Map.Entry<String, VirtualProperty> entry : virtualProperties.entrySet()) {
             if (entry.getValue().getPriorities() == null ||
                     entry.getValue().getPriorities().size() == 0) {
@@ -159,6 +160,8 @@ public class SchemaToStorageFormatImpl implements SchemaToStorageFormat {
             // The schema for different properties in the list of Priority should be the same
             Priority priority = entry.getValue().getPriorities().get(0);
             String virtualPropertyPath = VirtualPropertyUtil.removeDataPrefix(entry.getKey());
+            hasVirtualDefaultLocation |= VirtualPropertyUtil.isPropertyPathMatched(virtualPropertyPath, VirtualPropertyUtil.VIRTUAL_DEFAULT_LOCATION);
+
             String originalPropertyPath = VirtualPropertyUtil.removeDataPrefix(priority.getPath());
             List<Map<String, Object>> matchedItems = storageSchemaItems.stream().filter(item ->
                             VirtualPropertyUtil.isPropertyPathMatched((String) item.get("path"), originalPropertyPath))
@@ -167,6 +170,18 @@ public class SchemaToStorageFormatImpl implements SchemaToStorageFormat {
                             cloneVirtualProperty(item, virtualPropertyPath, originalPropertyPath))
                     .collect(Collectors.toList()));
         }
+
+        if(hasVirtualDefaultLocation) {
+            Map<String, Object> thumbnailProperty = new HashMap<>();
+            thumbnailProperty.put("path", VirtualPropertyUtil.VIRTUAL_DEFAULT_LOCATION_THUMBNAIL_PATH);
+            thumbnailProperty.put("kind", "core:dl:geoshape:1.0.0");
+            storageSchemaItems.add(thumbnailProperty);
+
+            Map<String, Object> isDecimatedProperty = new HashMap<>();
+            isDecimatedProperty.put("path", VirtualPropertyUtil.VIRTUAL_DEFAULT_LOCATION_IS_DECIMATED_PATH);
+            isDecimatedProperty.put("kind", "boolean");
+            storageSchemaItems.add(isDecimatedProperty);
+        }
     }
 
     private Map<String, Object> cloneVirtualProperty(Map<String, Object> originalProperty, String virtualPropertyPath, String originalPropertyPath) {
diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerServiceImpl.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerServiceImpl.java
index d04a2c9d449d7b58387443df241ee3a51a221755..4665931048e453d571409d5bc77248a89db6b4a0 100644
--- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerServiceImpl.java
+++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/IndexerServiceImpl.java
@@ -279,6 +279,8 @@ public class IndexerServiceImpl implements IndexerService {
 
         RecordIndexerPayload.Record document = null;
 
+
+        String recordJson = gson.toJson(storageRecord);
         try {
             Map<String, Object> storageRecordData = storageRecord.getData();
             document = new RecordIndexerPayload.Record();
diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageIndexerPayloadMapper.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageIndexerPayloadMapper.java
index ba1bed75630cdd8c5413ee5c17a747b4999a8a7a..d16cdf644ef2f37b9c976582149c38da031c3616 100644
--- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageIndexerPayloadMapper.java
+++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/service/StorageIndexerPayloadMapper.java
@@ -1,5 +1,6 @@
 package org.opengroup.osdu.indexer.service;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import org.apache.commons.beanutils.NestedNullException;
 import org.apache.commons.beanutils.PropertyUtils;
 import org.apache.http.HttpStatus;
@@ -15,6 +16,8 @@ import org.opengroup.osdu.indexer.schema.converter.tags.Priority;
 import org.opengroup.osdu.indexer.schema.converter.tags.VirtualProperties;
 import org.opengroup.osdu.indexer.schema.converter.tags.VirtualProperty;
 import org.opengroup.osdu.indexer.util.VirtualPropertyUtil;
+import org.opengroup.osdu.indexer.util.geo.decimator.DecimatedResult;
+import org.opengroup.osdu.indexer.util.geo.decimator.GeoShapeDecimator;
 import org.springframework.stereotype.Component;
 
 import javax.inject.Inject;
@@ -22,15 +25,8 @@ import java.lang.reflect.InvocationTargetException;
 import java.util.*;
 import java.util.stream.Collectors;
 
-import static org.opengroup.osdu.indexer.service.IAttributeParsingService.DATA_GEOJSON_TAG;
-import static org.opengroup.osdu.indexer.service.IAttributeParsingService.RECORD_GEOJSON_TAG;
-
 @Component
 public class StorageIndexerPayloadMapper {
-    private static final String DATA_VIRTUAL_DEFAULT_LOCATION = "data.VirtualProperties.DefaultLocation";
-    private static final String VIRTUAL_DEFAULT_LOCATION = "VirtualProperties.DefaultLocation";
-    private static final String FIELD_WGS84_COORDINATES = ".Wgs84Coordinates";
-
     @Inject
     private JaxRsDpsLog log;
     @Inject
@@ -42,6 +38,8 @@ public class StorageIndexerPayloadMapper {
     @Inject
     private IVirtualPropertiesSchemaCache virtualPropertiesSchemaCache;
 
+    private GeoShapeDecimator decimator = new GeoShapeDecimator();
+
     public Map<String, Object> mapDataPayload(IndexSchema storageSchema, Map<String, Object> storageRecordData,
                                               String recordId) {
 
@@ -53,7 +51,7 @@ public class StorageIndexerPayloadMapper {
         }
 
         mapDataPayload(storageSchema.getDataSchema(), storageRecordData, recordId, dataCollectorMap);
-        mapVirtualPropertiesPayload(storageSchema, dataCollectorMap);
+        mapVirtualPropertiesPayload(storageSchema, recordId, dataCollectorMap);
 
         return dataCollectorMap;
     }
@@ -191,7 +189,7 @@ public class StorageIndexerPayloadMapper {
         return type == ElasticType.TEXT;
     }
 
-    private void mapVirtualPropertiesPayload(IndexSchema storageSchema, Map<String, Object> dataCollectorMap) {
+    private void mapVirtualPropertiesPayload(IndexSchema storageSchema, String recordId, Map<String, Object> dataCollectorMap) {
         if (dataCollectorMap.isEmpty() || this.virtualPropertiesSchemaCache.get(storageSchema.getKind()) == null) {
             return;
         }
@@ -214,14 +212,30 @@ public class StorageIndexerPayloadMapper {
                 dataCollectorMap.put(virtualPropertyName, dataCollectorMap.get(originalPropertyName));
             });
         }
+
+        // Decimate the shape in VirtualProperties.DefaultLocation.Wgs84Coordinates when necessary
+        if(dataCollectorMap.containsKey(VirtualPropertyUtil.VIRTUAL_DEFAULT_LOCATION_WGS84_PATH)) {
+            try {
+                Map<String, Object> shapeObj = (Map<String, Object>)dataCollectorMap.get(VirtualPropertyUtil.VIRTUAL_DEFAULT_LOCATION_WGS84_PATH);
+                DecimatedResult result = decimator.decimateShapeObj(shapeObj);
+                if(result.isDecimated()) {
+                    dataCollectorMap.put(VirtualPropertyUtil.VIRTUAL_DEFAULT_LOCATION_WGS84_PATH, result.getDecimatedShapeObj());
+                }
+                dataCollectorMap.put(VirtualPropertyUtil.VIRTUAL_DEFAULT_LOCATION_THUMBNAIL_PATH, result.getThumbnailShapeObj());
+                dataCollectorMap.put(VirtualPropertyUtil.VIRTUAL_DEFAULT_LOCATION_IS_DECIMATED_PATH, result.isDecimated());
+            }
+            catch(JsonProcessingException ex) {
+                this.log.warning(String.format("record-id: %s | error decimating geoshape | error: %s", recordId, ex.getMessage()));
+            }
+        }
     }
 
     private Priority chooseOriginalProperty(String virtualPropertyPath, List<Priority> priorities, Map<String, Object> dataCollectorMap) {
-        if (VIRTUAL_DEFAULT_LOCATION.equals(virtualPropertyPath) || DATA_VIRTUAL_DEFAULT_LOCATION.equals(virtualPropertyPath)) {
+        if (VirtualPropertyUtil.VIRTUAL_DEFAULT_LOCATION.equals(virtualPropertyPath) || VirtualPropertyUtil.DATA_VIRTUAL_DEFAULT_LOCATION.equals(virtualPropertyPath)) {
             // Specially handle "data.VirtualProperties.DefaultLocation" -- check the value of the field "wgs84Coordinates"
             for (Priority priority : priorities) {
                 String originalPropertyPath = VirtualPropertyUtil.removeDataPrefix(priority.getPath());
-                String wgs84PropertyField = originalPropertyPath + FIELD_WGS84_COORDINATES;
+                String wgs84PropertyField = originalPropertyPath + VirtualPropertyUtil.FIELD_WGS84_COORDINATES;
                 if (dataCollectorMap.containsKey(wgs84PropertyField) && dataCollectorMap.get(wgs84PropertyField) != null)
                     return priority;
             }
diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/VirtualPropertyUtil.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/VirtualPropertyUtil.java
index 09d98312826b3f3196b07d01ae8934a85ab4fd79..93da7fed82195e8533ec8af7bf0f102e00b922a5 100644
--- a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/VirtualPropertyUtil.java
+++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/VirtualPropertyUtil.java
@@ -18,6 +18,14 @@ package org.opengroup.osdu.indexer.util;
 import com.google.api.client.util.Strings;
 
 public class VirtualPropertyUtil {
+    public static final String DATA_VIRTUAL_DEFAULT_LOCATION = "data.VirtualProperties.DefaultLocation";
+    public static final String VIRTUAL_DEFAULT_LOCATION = "VirtualProperties.DefaultLocation";
+    public static final String FIELD_WGS84_COORDINATES = ".Wgs84Coordinates";
+    public static final String VIRTUAL_DEFAULT_LOCATION_WGS84_PATH = VIRTUAL_DEFAULT_LOCATION + FIELD_WGS84_COORDINATES;
+    public static final String VIRTUAL_DEFAULT_LOCATION_THUMBNAIL_PATH = VIRTUAL_DEFAULT_LOCATION +  ".Thumbnail";
+    public static final String VIRTUAL_DEFAULT_LOCATION_IS_DECIMATED_PATH = VIRTUAL_DEFAULT_LOCATION + ".IsDecimated";
+
+
     private static final String PROPERTY_DELIMITER = ".";
     private static final String DATA_PREFIX = "data" + PROPERTY_DELIMITER;
 
diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/DecimatedResult.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/DecimatedResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..acf806a31b30628a478870b205501508aee931c5
--- /dev/null
+++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/DecimatedResult.java
@@ -0,0 +1,12 @@
+package org.opengroup.osdu.indexer.util.geo.decimator;
+
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class DecimatedResult {
+    Map<String, Object> decimatedShapeObj;
+    Map<String, Object> thumbnailShapeObj;
+    boolean isDecimated = false;
+}
diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/DouglasPeuckerReducer.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/DouglasPeuckerReducer.java
new file mode 100644
index 0000000000000000000000000000000000000000..845bd547d4a9ee5dfbf737170d412b501001a5a2
--- /dev/null
+++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/DouglasPeuckerReducer.java
@@ -0,0 +1,188 @@
+package org.opengroup.osdu.indexer.util.geo.decimator;
+
+import org.opengroup.osdu.indexer.model.geojson.Position;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class DouglasPeuckerReducer {
+
+    public static List<Integer> getPointIndexesToKeep(List<Position> coordinates, double xyScalar, double epsilon) {
+        List<Integer> pointIndexesToKeep = new ArrayList<>();
+
+        int firstPointIndex = 0;
+        int lastPointIndex = coordinates.size() - 1;
+        pointIndexesToKeep.add(firstPointIndex);
+        pointIndexesToKeep.add(lastPointIndex);
+
+        // The first and the last point cannot be the same when applying Doublas/Peucker algorithm
+        while(firstPointIndex < lastPointIndex && arePointsEqual(coordinates.get(firstPointIndex), coordinates.get(lastPointIndex)))
+            lastPointIndex--;
+
+        // Keep full resolution for very small objects, i.e. to preserve small rectangles which might be used for accuracy testing
+        if (lastPointIndex < 6)
+        {
+            for (int i = 1; i <= lastPointIndex; i++)
+                pointIndexesToKeep.add(i);
+        }
+        else {
+            // Save the last point used for reduction. It is ok to keep duplicate points
+            pointIndexesToKeep.add(lastPointIndex);
+
+            List<Integer> boundaryPointIndexes = getBoundaryPointIndexes(coordinates, firstPointIndex, lastPointIndex);
+            pointIndexesToKeep.addAll(boundaryPointIndexes);
+
+            reduce(coordinates, firstPointIndex, lastPointIndex, xyScalar, epsilon, pointIndexesToKeep);
+        }
+
+        // Make sure that the duplicate points are removed
+        return pointIndexesToKeep.stream().distinct().sorted().collect(Collectors.toList());
+    }
+
+    private static List<Integer> getBoundaryPointIndexes(List<Position> coordinates, int firstPointIndex, int lastPointIndex) {
+
+        double xMin = Double.NaN;
+        double xMax = Double.NaN;
+        double yMin = Double.NaN;
+        double yMax = Double.NaN;
+        double zMin = Double.NaN;
+        double zMax = Double.NaN;
+        int xMinIndex = firstPointIndex;
+        int xMaxIndex = firstPointIndex;
+        int yMinIndex = firstPointIndex;
+        int yMaxIndex = firstPointIndex;
+        int zMinIndex = firstPointIndex;
+        int zMaxIndex = firstPointIndex;
+        for(int i = firstPointIndex; i <= lastPointIndex; i++) {
+            Position position = coordinates.get(i);
+            double xValue = position.getLongitude();
+            double yValue = position.getLatitude();
+            double zValue = position.getAltitude();
+
+            if(!Double.isNaN(xValue)) {
+                if (Double.isNaN(xMin) || xValue < xMin)
+                {
+                    xMin = xValue;
+                    xMinIndex = i;
+                }
+
+                if (Double.isNaN(xMax) || xValue > xMax)
+                {
+                    xMax = xValue;
+                    xMaxIndex = i;
+                }
+            }
+
+            if (!Double.isNaN(yValue))
+            {
+                if (Double.isNaN(yMin) || yValue < yMin)
+                {
+                    yMin = yValue;
+                    yMinIndex = i;
+                }
+
+                if (Double.isNaN(yMax) || yValue > yMax)
+                {
+                    yMax = yValue;
+                    yMaxIndex = i;
+                }
+            }
+
+            if (!Double.isNaN(zValue))
+            {
+                if (Double.isNaN(zMin) || zValue < zMin)
+                {
+                    zMin = zValue;
+                    zMinIndex = i;
+                }
+
+                if (Double.isNaN(zMax) || zValue > zMax)
+                {
+                    zMax = zValue;
+                    zMaxIndex = i;
+                }
+            }
+        }
+
+        Set<Integer> boundaryPointIndexes = new HashSet<> (Arrays.asList(xMinIndex, xMaxIndex, yMinIndex, yMaxIndex, zMinIndex, zMaxIndex));
+        return new ArrayList<>(boundaryPointIndexes);
+    }
+
+    private static void reduce(List<Position> coordinates, int firstPointIndex, int lastPointIndex, double xyScalar, double epsilon, List<Integer> pointIndexesToKeep) {
+        double maxDistance = 0d;
+        int indexFarthest = 0;
+
+        Position startPoint = coordinates.get(firstPointIndex);
+        Position endPoint = coordinates.get(lastPointIndex);
+        for (int index = firstPointIndex + 1; index < lastPointIndex; index++)
+        {
+            Position testPoint = coordinates.get(index);
+            double distance = calculatePerpendicularDistance(startPoint, endPoint, testPoint, xyScalar);
+            if (distance > maxDistance)
+            {
+                maxDistance = distance;
+                indexFarthest = index;
+            }
+        }
+
+        if (maxDistance > epsilon && indexFarthest != 0)
+        {
+            // Add the largest point that exceeds the tolerance
+            pointIndexesToKeep.add(indexFarthest);
+
+            // Bisect and recur
+            reduce(coordinates, firstPointIndex, indexFarthest, xyScalar, epsilon, pointIndexesToKeep);
+            reduce(coordinates, indexFarthest, lastPointIndex, xyScalar, epsilon, pointIndexesToKeep);
+        }
+    }
+
+    /// <summary>
+    /// Calculates the perpendicular distance between a point and a line
+    /// http://www.softsurfer.com/Archive/algorithm_0102/
+    /// </summary>
+    /// <returns></returns>
+    private static double calculatePerpendicularDistance(Position startPoint, Position endPoint, Position testPoint, double xyScalar) {
+        boolean hasAltitude = startPoint.hasAltitude();
+        double lineStartX = startPoint.getLongitude() * xyScalar;
+        double lineStartY = startPoint.getLatitude() * xyScalar;
+        double lineStartZ = startPoint.getAltitude();
+
+        double lineEndX = endPoint.getLongitude() * xyScalar;
+        double lineEndY = endPoint.getLatitude() * xyScalar;
+        double lineEndZ = endPoint.getAltitude();
+
+        double testPointX = testPoint.getLongitude() * xyScalar;
+        double testPointY = testPoint.getLatitude() * xyScalar;
+        double testPointZ = testPoint.getAltitude();
+
+        double vX = lineEndX - lineStartX;
+        double vY = lineEndY - lineStartY;
+        double vZ = lineEndZ - lineStartZ;
+
+        double wX = testPointX - lineStartX;
+        double wY = testPointY - lineStartY;
+        double wZ = testPointZ - lineStartZ;
+
+        double c1 = (wX * vX) + (wY * vY) + (hasAltitude ? wZ * vZ : 0);
+        double c2 = (vX * vX) + (vY * vY) + (hasAltitude ? vZ * vZ : 0);
+
+        if (c2 == 0)
+        {
+            // It should not happen if start and end are not at the same point - return the distance between the test point and the line start point
+            return Math.sqrt(Math.pow(testPointX - lineStartX, 2) + Math.pow(testPointY - lineStartY, 2) + (hasAltitude ? Math.pow(testPointZ - lineStartZ, 2) : 0));
+        }
+
+        double b = c1 / c2;
+        double pbX = lineStartX + b * vX;
+        double pbY = lineStartY + b * vY;
+        double pbZ = lineStartZ + b * vZ;
+        return Math.sqrt(Math.pow(pbX - testPointX, 2) + Math.pow(pbY - testPointY, 2) + (hasAltitude ? Math.pow(pbZ - testPointZ, 2) : 0));
+    }
+
+    private static boolean arePointsEqual(Position point1, Position point2)
+    {
+        return point1.getLatitude() == point2.getLatitude() &&
+                point1.getLongitude() == point2.getLongitude() &&
+                (!point1.hasAltitude() || point1.getAltitude() == point2.getAltitude());
+    }
+}
diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/GeoShapeDecimator.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/GeoShapeDecimator.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a2e00692778f75902eb128793db0578484a9d98
--- /dev/null
+++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/GeoShapeDecimator.java
@@ -0,0 +1,56 @@
+package org.opengroup.osdu.indexer.util.geo.decimator;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.NamedType;
+import org.opengroup.osdu.indexer.model.geojson.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+public class GeoShapeDecimator {
+    private ObjectMapper deserializerMapper;
+    private ObjectMapper serializerMapper;
+    private GeometryDecimator decimator;
+
+
+    public GeoShapeDecimator() {
+        decimator = new GeometryDecimator();
+        serializerMapper = new ObjectMapper();
+        deserializerMapper = createDeserializerMapper();
+    }
+
+    public DecimatedResult decimateShapeObj(Map<String, Object> shapeObj) throws JsonProcessingException {
+        DecimatedResult result = new DecimatedResult();
+        String type = (String)shapeObj.getOrDefault("type", null);
+        if(type != null && type.equals("geometrycollection")) {
+            GeometryCollection geometryCollection = deserializerMapper.readValue(deserializerMapper.writeValueAsString(shapeObj), GeometryCollection.class);
+            boolean decimated = decimator.decimate(geometryCollection);
+            if (decimated) {
+                result.setDecimatedShapeObj(
+                        serializerMapper.readValue(serializerMapper.writeValueAsString(geometryCollection), new TypeReference<Map<String, Object>>() {}));
+                result.setDecimated(true);
+            }
+            // Must serialize the normal decimated shape before decimating it as thumbnail
+            decimator.decimateAsThumbnail(geometryCollection);
+            result.setThumbnailShapeObj(
+                    serializerMapper.readValue(serializerMapper.writeValueAsString(geometryCollection), new TypeReference<Map<String, Object>>() {}));
+        }
+
+        return result;
+    }
+
+    @NotNull
+    private static ObjectMapper createDeserializerMapper() {
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.registerSubtypes(new NamedType(GeometryCollection.class, "geometrycollection"));
+        mapper.registerSubtypes(new NamedType(Polygon.class, "polygon"));
+        mapper.registerSubtypes(new NamedType(MultiPolygon.class, "multipolygon"));
+        mapper.registerSubtypes(new NamedType(LineString.class, "linestring"));
+        mapper.registerSubtypes(new NamedType(MultiLineString.class, "multilinestring"));
+        mapper.registerSubtypes(new NamedType(Point.class, "point"));
+        mapper.registerSubtypes(new NamedType(MultiPoint.class, "multipoint"));
+        return mapper;
+    }
+}
diff --git a/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/GeometryDecimator.java b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/GeometryDecimator.java
new file mode 100644
index 0000000000000000000000000000000000000000..9ace05f6e66005f9d203b2dcbe1baa523863a002
--- /dev/null
+++ b/indexer-core/src/main/java/org/opengroup/osdu/indexer/util/geo/decimator/GeometryDecimator.java
@@ -0,0 +1,145 @@
+package org.opengroup.osdu.indexer.util.geo.decimator;
+
+import org.opengroup.osdu.indexer.model.geojson.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GeometryDecimator {
+    private static final double NormalShapeDecimationEpsilon = 10; // meters
+    private static final double ThumbnailShapeDecimationEpsilon = 1000; // meters
+    private static final double DegreesToMeters = 100000; // approximate using 100km per degree
+    private static final int MaxShapePointCountForLineDecimation = 300000;
+
+    public boolean decimate(GeometryCollection geometryCollection) {
+        return decimate(geometryCollection, NormalShapeDecimationEpsilon);
+    }
+
+    public boolean decimateAsThumbnail(GeometryCollection geometryCollection) {
+        return decimate(geometryCollection, ThumbnailShapeDecimationEpsilon);
+    }
+
+    private boolean decimate(GeometryCollection geometryCollection, double epsilon) {
+        if(geometryCollection == null || geometryCollection.getGeometries() == null)
+            return false;
+
+        boolean decimated = false;
+        for(GeoJsonObject geoJsonObject: geometryCollection.getGeometries()) {
+            decimated |= decimateBasicGeometry(geoJsonObject, epsilon);
+        }
+        return decimated;
+    }
+
+    private boolean decimate(MultiLineString geometry, double epsilon) {
+        if(geometry == null || geometry.getCoordinates() == null)
+            return false;
+
+        boolean decimated = false;
+        for(List<Position> coordinates : geometry.getCoordinates()) {
+            decimated |= decimateLine(coordinates, epsilon);
+        }
+        return decimated;
+    }
+
+    private boolean decimate(LineString geometry, double epsilon) {
+        if(geometry == null)
+            return false;
+
+        return decimateLine(geometry.getCoordinates(), epsilon);
+    }
+
+    private boolean decimate(MultiPolygon geometry, double epsilon) {
+        if(geometry == null || geometry.getCoordinates() == null)
+            return false;
+
+        boolean decimated = false;
+        for(List<List<Position>> polygon : geometry.getCoordinates()) {
+            for(List<Position> coordinates : polygon) {
+                decimated |= decimateLine(coordinates, epsilon);
+            }
+        }
+        return decimated;
+    }
+
+    private boolean decimate(Polygon geometry, double epsilon) {
+        if(geometry == null || geometry.getCoordinates() == null)
+            return false;
+
+        boolean decimated = false;
+        for(List<Position> coordinates : geometry.getCoordinates()) {
+            decimated |= decimateLine(coordinates, epsilon);
+        }
+        return decimated;
+    }
+
+    private boolean decimate(Point geometry, double epsilon) {
+        return false;
+    }
+
+    private boolean decimate(MultiPoint geometry, double epsilon) {
+        return false;
+    }
+
+    private boolean decimateBasicGeometry(GeoJsonObject geometry, double epsilon) {
+        if(geometry instanceof MultiLineString) {
+            return decimate((MultiLineString) geometry, epsilon);
+        }
+        else if(geometry instanceof LineString) {
+            return decimate((LineString)geometry, epsilon);
+        }
+        else if(geometry instanceof MultiPolygon) {
+            return decimate((MultiPolygon)geometry, epsilon);
+        }
+        else if(geometry instanceof Polygon) {
+            return decimate((Polygon)geometry, epsilon);
+        }
+        else if(geometry instanceof Point) {
+            return decimate((Point)geometry, epsilon);
+        }
+        else if(geometry instanceof MultiPoint) {
+            return decimate((MultiPoint)geometry, epsilon);
+        }
+        else
+            return false;
+    }
+
+    private boolean decimateLine(List<Position> coordinates, double epsilon) {
+        if(coordinates == null || coordinates.size() < 3)
+            return false;
+
+        // Douglas/Peucker algorithm is expensive, apply simple sampling if the line has too many points
+        coordinates = downSamplePoints(coordinates);
+
+        List<Integer> pointIndexes = DouglasPeuckerReducer.getPointIndexesToKeep(coordinates, DegreesToMeters, epsilon);
+        List<Position> sampledCoordinates = new ArrayList<>();
+        for(int i : pointIndexes) {
+            sampledCoordinates.add(coordinates.get(i));
+        }
+
+        boolean decimated = (coordinates.size() != sampledCoordinates.size());
+        if(decimated) {
+            coordinates.clear();
+            coordinates.addAll(sampledCoordinates);
+        }
+        return decimated;
+    }
+
+    private List<Position> downSamplePoints(List<Position> coordinates) {
+        //Don't sample it if the number of point is not much larger than MaxShapePointCountForLineDecimation
+        if (coordinates.size() <= MaxShapePointCountForLineDecimation * 1.2) {
+            return coordinates;
+        }
+
+        List<Position> sampledPoints = new ArrayList<>();
+        int interval = (int)Math.ceil(coordinates.size() / (double)MaxShapePointCountForLineDecimation);
+        int i = 0;
+        for(; i < coordinates.size(); i += interval) {
+            sampledPoints.add(coordinates.get(i));
+        }
+        // Add the last point
+        sampledPoints.add(coordinates.get(coordinates.size() -1));
+        return sampledPoints;
+    }
+
+
+}
diff --git a/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/GeometryDecimatorUtilTest.java b/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/GeometryDecimatorUtilTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..dcc7829b121551dc6eecb688f1a252bf1230ad2c
--- /dev/null
+++ b/indexer-core/src/test/java/org/opengroup/osdu/indexer/util/GeometryDecimatorUtilTest.java
@@ -0,0 +1,77 @@
+package org.opengroup.osdu.indexer.util;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import lombok.SneakyThrows;
+import org.junit.Test;
+import org.opengroup.osdu.indexer.model.geojson.*;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class GeometryUtilTest {
+    Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
+
+    GeometryDecimatorUtil util = new GeometryDecimatorUtil();
+
+    @Test
+    public void test1() {
+        String geoShapeJson = getGeoShapeFromFile("multi_line_string_projection_meter.json");
+        Type type = new TypeToken<Map<String, Object>>() {}.getType();
+        Map<String, Object> shapeObj = gson.fromJson(geoShapeJson, type);
+        try {
+            boolean decimated = util.decimateShapeObj(shapeObj);
+        } catch (JsonProcessingException e) {
+            e.printStackTrace();
+        }
+
+
+    }
+
+    private void replaceCoordinates(Map<String, Object> featureCollection, Map<String, Object> geometryCollection) {
+        ArrayList features =(ArrayList)featureCollection.get("features");
+        for(Object feature: features) {
+
+        }
+    }
+
+    private int getTotalSize(FeatureCollection featureCollection) {
+        int size = 0;
+        for (Feature feature: featureCollection) {
+            GeoJsonObject geometry = feature.getGeometry();
+            if(geometry instanceof MultiLineString) {
+                MultiLineString multiLineString = ((MultiLineString) geometry);
+                for(List<Position> coordinates : multiLineString.getCoordinates()) {
+                    size += coordinates.size();
+                }
+            }
+            else if(geometry instanceof LineString) {
+                LineString lineString = ((LineString)geometry);
+                size += lineString.getCoordinates().size();
+            }
+        }
+        return size;
+    }
+
+
+    @SneakyThrows
+    private String getGeoShapeFromFile(String file) {
+        InputStream inStream = this.getClass().getResourceAsStream("/geometry-decimation/" + file);
+        BufferedReader br = new BufferedReader(new InputStreamReader(inStream));
+        StringBuilder stringBuilder = new StringBuilder();
+        String sCurrentLine;
+        while ((sCurrentLine = br.readLine()) != null)
+        {
+            stringBuilder.append(sCurrentLine).append("\n");
+        }
+        return stringBuilder.toString();
+    }
+}