eu.europa.ec.fisheries.uvms.spatial.service.bean.impl.AreaServiceBean.java Source code

Java tutorial

Introduction

Here is the source code for eu.europa.ec.fisheries.uvms.spatial.service.bean.impl.AreaServiceBean.java

Source

/*
Developed by the European Commission - Directorate General for Maritime Affairs and Fisheries @ European Union, 2015-2016.
    
This file is part of the Integrated Fisheries Data Management (IFDM) Suite. The IFDM Suite is free software: you can redistribute it 
and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of 
the License, or any later version. The IFDM Suite is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
details. You should have received a copy of the GNU General Public License along with the IFDM Suite. If not, see <http://www.gnu.org/licenses/>.
    
 */

package eu.europa.ec.fisheries.uvms.spatial.service.bean.impl;

import static com.vividsolutions.jts.operation.distance.DistanceOp.nearestPoints;
import static eu.europa.ec.fisheries.uvms.commons.geometry.utils.GeometryUtils.isDefaultEpsgSRID;
import static org.geotools.geometry.jts.JTS.orthodromicDistance;

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier;
import eu.europa.ec.fisheries.uvms.commons.domain.BaseEntity;
import eu.europa.ec.fisheries.uvms.commons.geometry.mapper.GeometryMapper;
import eu.europa.ec.fisheries.uvms.commons.geometry.utils.GeometryUtils;
import eu.europa.ec.fisheries.uvms.commons.service.exception.ServiceException;
import eu.europa.ec.fisheries.uvms.commons.service.fileutils.ZipExtractor;
import eu.europa.ec.fisheries.uvms.commons.service.interceptor.SimpleTracingInterceptor;
import eu.europa.ec.fisheries.uvms.commons.service.interceptor.ValidationInterceptor;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.Area;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.AreaByLocationSpatialRQ;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.AreaExtendedIdentifierType;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.AreaIdentifierType;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.AreaSimpleType;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.AreaType;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.AreaTypeEntry;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.ClosestLocationSpatialRQ;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.FilterAreasSpatialRQ;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.FilterAreasSpatialRS;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.Location;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.LocationType;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.LocationTypeEntry;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.ScopeAreasType;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.UnitType;
import eu.europa.ec.fisheries.uvms.spatial.model.schemas.UserAreasType;
import eu.europa.ec.fisheries.uvms.spatial.service.bean.AreaService;
import eu.europa.ec.fisheries.uvms.spatial.service.bean.AreaTypeNamesService;
import eu.europa.ec.fisheries.uvms.spatial.service.bean.SpatialRepository;
import eu.europa.ec.fisheries.uvms.spatial.service.dao.AbstractAreaDao;
import eu.europa.ec.fisheries.uvms.spatial.service.dao.util.DAOFactory;
import eu.europa.ec.fisheries.uvms.spatial.service.dao.util.DatabaseDialect;
import eu.europa.ec.fisheries.uvms.spatial.service.dao.util.DatabaseDialectFactory;
import eu.europa.ec.fisheries.uvms.spatial.service.dto.area.GenericSystemAreaDto;
import eu.europa.ec.fisheries.uvms.spatial.service.dto.area.SystemAreaNamesDto;
import eu.europa.ec.fisheries.uvms.spatial.service.dto.upload.UploadMapping;
import eu.europa.ec.fisheries.uvms.spatial.service.dto.upload.UploadMetadata;
import eu.europa.ec.fisheries.uvms.spatial.service.dto.upload.UploadProperty;
import eu.europa.ec.fisheries.uvms.spatial.service.entity.AreaLocationTypesEntity;
import eu.europa.ec.fisheries.uvms.spatial.service.entity.BaseAreaEntity;
import eu.europa.ec.fisheries.uvms.spatial.service.entity.CountryEntity;
import eu.europa.ec.fisheries.uvms.spatial.service.entity.EntityFactory;
import eu.europa.ec.fisheries.uvms.spatial.service.entity.PortEntity;
import eu.europa.ec.fisheries.uvms.spatial.service.enums.MeasurementUnit;
import eu.europa.ec.fisheries.uvms.spatial.service.enums.SpatialTypeEnum;
import eu.europa.ec.fisheries.uvms.spatial.service.exception.SpatialServiceErrors;
import eu.europa.ec.fisheries.uvms.spatial.service.exception.SpatialServiceException;
import eu.europa.ec.fisheries.uvms.spatial.service.util.UploadUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.FeatureSource;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.referencing.CRS;
import org.geotools.referencing.GeodeticCalculator;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

@Stateless
@Local(AreaService.class)
@Slf4j
public class AreaServiceBean implements AreaService {

    private static final String MULTIPOINT = "MULTIPOINT";

    private static final String GID = "gid";
    private static final String AREA_TYPE = "areaType";

    private EntityManager em;

    @PersistenceContext(unitName = "spatialPUpostgres")
    private EntityManager postgres;

    @PersistenceContext(unitName = "spatialPUoracle")
    private EntityManager oracle;

    @EJB
    private AreaTypeNamesService areaTypeService;

    @EJB
    private AreaService areaService;

    @EJB
    private SpatialRepository repository;

    @EJB
    private PropertiesBean properties;

    private DatabaseDialect databaseDialect;

    public void initEntityManager() {
        String dbDialect = System.getProperty("db.dialect");
        if ("oracle".equalsIgnoreCase(dbDialect)) {
            em = oracle;
        } else {
            em = postgres;
        }
    }

    @PostConstruct
    public void init() {
        initEntityManager();
        databaseDialect = new DatabaseDialectFactory(properties).getInstance();
    }

    @Override
    public Map<String, String> getAllCountriesDesc() throws ServiceException {
        Map<String, String> stringStringMap = new HashMap<>();
        List<CountryEntity> entityByNamedQuery = repository.findAllCountries();
        for (CountryEntity country : entityByNamedQuery) {
            stringStringMap.put(country.getCode(), country.getName());
        }
        return stringStringMap;
    }

    @Override
    // FIXME rewrite me please and re use existing code
    public List<Map<String, Object>> getAreasByIds(final List<AreaTypeEntry> areaTypes) throws ServiceException {

        List<Map<String, Object>> areaColumnsList = new ArrayList<>();
        Map<String, List<Long>> areaTypeGidMap = new HashMap<>();

        for (AreaTypeEntry areaTypeEntry : areaTypes) {
            String gid = areaTypeEntry.getId();
            String areaType = areaTypeEntry.getAreaType().value();

            if (!StringUtils.isNumeric(gid)) {
                throw new SpatialServiceException(SpatialServiceErrors.INVALID_ID_TYPE, gid);
            }

            boolean isAdded = false;
            for (Map.Entry<String, List<Long>> entry : areaTypeGidMap.entrySet()) {
                if (entry.getKey().equalsIgnoreCase(areaType)) {
                    entry.getValue().add(Long.parseLong(gid));
                    isAdded = true;
                }
            }
            if (!isAdded) {
                areaTypeGidMap.put(areaType, new ArrayList(Arrays.asList(Long.parseLong(gid))));
            }
        }

        for (Map.Entry<String, List<Long>> entry : areaTypeGidMap.entrySet()) { // FIXME looping and querying should be avoided
            String namedQuery = null;
            for (SpatialTypeEnum type : SpatialTypeEnum.values()) {
                if (type.getType().equalsIgnoreCase(entry.getKey())) {
                    namedQuery = type.getNamedQuery();
                }
            }
            if (namedQuery != null) {
                List<Map<String, Object>> selectedAreaColumns = repository.getAreasByIds(namedQuery,
                        entry.getValue());
                for (Map<String, Object> columnMap : selectedAreaColumns) {
                    columnMap.put(AREA_TYPE, entry.getKey().toUpperCase());
                }
                areaColumnsList.addAll(selectedAreaColumns);
            }
        }
        return areaColumnsList;
    }

    @Override
    @Interceptors(SimpleTracingInterceptor.class)
    public void upload(final UploadMapping mapping, final String type, final Integer epsg) throws ServiceException {

        String ref = (String) mapping.getAdditionalProperties().get("ref");

        try {
            Integer srid = repository.mapEpsgToSRID(epsg);
            AreaLocationTypesEntity typeName = repository.findAreaLocationTypeByTypeName(type.toUpperCase());

            if (typeName == null) {
                throw new ServiceException("TYPE NOT SUPPORTED");
            }

            Map<String, List<Property>> features = readShapeFile(
                    Paths.get(ref + File.separator + typeName.getAreaDbTable() + ".shp"), srid);
            DAOFactory.getAbstractSpatialDao(em, typeName.getTypeName()).bulkInsert(features, mapping.getMapping());
            org.apache.commons.io.FileUtils.deleteDirectory(Paths.get(ref).getParent().toFile());

            repository.makeGeomValid(typeName.getAreaDbTable(), databaseDialect);

        } catch (Exception e) {
            throw new ServiceException(e.getMessage(), e);
        }
    }

    private Map<String, List<Property>> readShapeFile(Path shapeFilePath, Integer srid)
            throws IOException, ServiceException {

        Map<String, Object> map = new HashMap<>();
        map.put("url", shapeFilePath.toUri().toURL());
        DataStore dataStore = DataStoreFinder.getDataStore(map);
        Map<String, List<Property>> geometries = new HashMap<>();
        String typeName = dataStore.getTypeNames()[0];
        FeatureSource<SimpleFeatureType, SimpleFeature> source = dataStore.getFeatureSource(typeName);
        FeatureCollection<SimpleFeatureType, SimpleFeature> collection = source.getFeatures(Filter.INCLUDE);
        FeatureIterator<SimpleFeature> iterator = collection.features();

        try {
            CoordinateReferenceSystem sourceCRS = GeometryUtils.toCoordinateReferenceSystem(srid);
            CoordinateReferenceSystem coordinateReferenceSystem = GeometryUtils
                    .toDefaultCoordinateReferenceSystem();
            MathTransform transform = CRS.findMathTransform(sourceCRS, coordinateReferenceSystem);
            while (iterator.hasNext()) {
                final SimpleFeature feature = iterator.next();
                geometries.put(feature.getID(), new ArrayList<>(feature.getProperties()));
                Geometry targetGeometry = (Geometry) feature.getDefaultGeometry();
                if (targetGeometry != null) {
                    if (coordinateReferenceSystem.getName().equals(sourceCRS.getName())) {
                        targetGeometry = JTS.transform(targetGeometry, transform);
                    }
                } else {
                    throw new ServiceException("TARGET GEOMETRY CANNOT BE NULL");
                }
                targetGeometry.setSRID(databaseDialect.defaultSRID());
                feature.setDefaultGeometry(targetGeometry);
            }
            return geometries;

        } catch (FactoryException | TransformException e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(e.getMessage(), e);
        } finally {
            iterator.close();
            dataStore.dispose();
        }
    }

    @Override
    @Interceptors(SimpleTracingInterceptor.class)
    public UploadMetadata metadata(byte[] bytes, String type) {

        UploadMetadata response = new UploadMetadata();

        try {
            if (bytes.length == 0) {
                throw new IllegalArgumentException("File is empty.");
            }
            AreaLocationTypesEntity typeName = repository.findAreaLocationTypeByTypeName(type.toUpperCase());

            if (typeName == null) {
                throw new ServiceException("TYPE NOT SUPPORTED");
            }

            BaseEntity instance = EntityFactory.getInstance(typeName.getTypeName());
            List<Field> properties = instance.listMembers();

            for (Field field : properties) {
                UploadProperty uploadProperty = new UploadProperty();
                uploadProperty.withName(field.getName()).withType(field.getType().getSimpleName());
                response.getDomain().add(uploadProperty);
            }

            Path uploadPath = Paths.get("app/upload/spatial");
            uploadPath.toFile().mkdirs();
            Path absolutePath = Files.createTempDirectory(uploadPath, null, new FileAttribute[0]);
            ZipExtractor.unZipFile(bytes, absolutePath);
            ZipExtractor.renameFiles(absolutePath, typeName.getTypeName());
            List<UploadProperty> propertyList = UploadUtil.readAttribute(absolutePath);
            response.getFile().addAll(propertyList);
            response.withAdditionalProperty("ref", absolutePath.toString());

        } catch (Exception ex) {
            throw new SpatialServiceException(SpatialServiceErrors.INVALID_UPLOAD_AREA_DATA, ex);
        }
        return response;
    }

    @Override
    @Interceptors(ValidationInterceptor.class)
    public Map<String, Object> getAreaById(@NotNull Long id, @NotNull AreaType areaType) throws ServiceException {

        BaseAreaEntity area = repository.findAreaById(id, areaType);

        if (area == null) {
            throw new SpatialServiceException(SpatialServiceErrors.ENTITY_NOT_FOUND, areaType.value());
        }

        ObjectMapper objectMapper = new ObjectMapper();
        @SuppressWarnings("unchecked")
        Map<String, Object> map = objectMapper.convertValue(area, Map.class);
        return map;
    }

    @Override
    public List<AreaSimpleType> getAreasByCode(List<AreaSimpleType> areaSimpleTypeList) throws ServiceException {

        List records = repository.areaByCode(areaSimpleTypeList);
        List<AreaSimpleType> simpleTypeList = new ArrayList<>();

        for (Object record : records) {

            final Object[] result = (Object[]) record;
            String type = (String) result[0];
            String code = (String) result[1];
            Geometry geom = (Geometry) result[2];
            simpleTypeList
                    .add(new AreaSimpleType(type, code, GeometryMapper.INSTANCE.geometryToWkt(geom).getValue()));
        }

        return simpleTypeList;
    }

    @Override // Fixme duplicated method
    public List<Location> getClosestPointByPoint(final ClosestLocationSpatialRQ request) throws ServiceException {

        Double lat = request.getPoint().getLatitude();
        Double lon = request.getPoint().getLongitude();
        Integer crs = request.getPoint().getCrs();
        UnitType unit = request.getUnit();

        Point incoming = (Point) GeometryUtils.toGeographic(lat, lon, crs);
        Double incomingLatitude = incoming.getY();
        Double incomingLongitude = incoming.getX();
        Map<String, Location> distancePerTypeMap = new HashMap<>();
        MeasurementUnit measurementUnit = MeasurementUnit.getMeasurement(unit.name());
        GeodeticCalculator calc = new GeodeticCalculator(GeometryUtils.toDefaultCoordinateReferenceSystem());
        List<AreaLocationTypesEntity> typeEntities = repository.findByIsLocationAndIsSystemWide(true, true);

        List records = repository.closestPointByPoint(typeEntities, databaseDialect, incoming);

        for (Object record : records) {
            Object[] result = (Object[]) record;
            Point centroid = ((Geometry) result[4]).getCentroid();
            calc.setStartingGeographicPoint(centroid.getX(), centroid.getY());
            calc.setDestinationGeographicPoint(incomingLongitude, incomingLatitude);
            Double orthodromicDistance = calc.getOrthodromicDistance();
            String type = result[0].toString();
            Location closest = distancePerTypeMap.get(type);

            if (closest == null || orthodromicDistance / measurementUnit.getRatio() < closest.getDistance()) {
                if (closest == null) {
                    closest = new Location();
                }
                closest.setDistance(orthodromicDistance);
                closest.setId(result[1].toString());
                closest.setDistance(orthodromicDistance / measurementUnit.getRatio());
                closest.setUnit(unit);
                closest.setCode(result[2].toString());
                closest.setName(result[3].toString());
                closest.setLocationType(LocationType.fromValue(type));
                distancePerTypeMap.put(type, closest);
            }
        }
        return new ArrayList<>(distancePerTypeMap.values());
    }

    @Override
    public Map<String, Object> getClosestPointByPoint(@NotNull Double longitude, @NotNull Double latitude,
            @NotNull Integer crs) throws ServiceException {
        Point point = (Point) GeometryUtils.toGeographic(latitude, longitude, crs);
        GeodeticCalculator calc = new GeodeticCalculator(GeometryUtils.toDefaultCoordinateReferenceSystem());
        List<PortEntity> records = repository.listClosestPorts(point, 5);
        Double closestDistance = Double.MAX_VALUE;
        PortEntity match = null;
        for (PortEntity portsEntity : records) {
            Geometry geometry = portsEntity.getGeom();
            Point centroid = geometry.getCentroid();
            calc.setStartingGeographicPoint(centroid.getX(), centroid.getY());
            calc.setDestinationGeographicPoint(point.getX(), point.getY());
            Double orthodromicDistance = calc.getOrthodromicDistance();
            if (closestDistance > orthodromicDistance) {
                closestDistance = orthodromicDistance;
                match = portsEntity;
            }
        }
        ObjectMapper objectMapper = new ObjectMapper();
        Map map = objectMapper.convertValue(match, Map.class);
        return map;
    }

    @Override
    @Interceptors(ValidationInterceptor.class) // TODO add isLocation and IsSystemWide as parameters
    public List<Area> getClosestArea(@NotNull Double longitude, @NotNull Double latitude, @NotNull Integer crs,
            @NotNull UnitType unit) throws ServiceException {
        final Map<String, Area> distancePerTypeMap = new HashMap<>();
        try {
            Point incoming = (Point) GeometryUtils.toGeographic(latitude, longitude, crs);
            MeasurementUnit measurementUnit = MeasurementUnit.getMeasurement(unit.name());
            List<AreaLocationTypesEntity> typesEntities = repository.findByIsLocationAndIsSystemWide(false, true);
            List records = repository.closestAreaByPoint(typesEntities, databaseDialect, incoming);
            for (Object record : records) {
                Object[] result = (Object[]) record;
                Geometry geom = (Geometry) result[4];
                if (geom.isEmpty()) {
                    continue;
                }
                com.vividsolutions.jts.geom.Coordinate[] coordinates = nearestPoints(geom, incoming);
                Double orthodromicDistance = orthodromicDistance(coordinates[0], coordinates[1],
                        GeometryUtils.toDefaultCoordinateReferenceSystem());
                String type = result[0].toString();
                Area closest = distancePerTypeMap.get(type);
                if (closest == null || orthodromicDistance / measurementUnit.getRatio() < closest.getDistance()) {
                    if (closest == null) {
                        closest = new Area();
                    }
                    closest.setDistance(orthodromicDistance);
                    closest.setId(result[1].toString());
                    closest.setDistance(orthodromicDistance / measurementUnit.getRatio());
                    closest.setUnit(unit);
                    closest.setCode(result[2] != null ? result[2].toString() : null);
                    closest.setName(result[3] != null ? result[3].toString() : null);
                    closest.setAreaType(AreaType.valueOf(type));
                    distancePerTypeMap.put(type, closest);
                }
            }
        } catch (TransformException e) {
            throw new ServiceException("ERROR WHILE CALCULATING DISTANCE", e);
        }
        return new ArrayList<>(distancePerTypeMap.values());
    }

    @Override
    @Transactional(Transactional.TxType.REQUIRES_NEW)
    public FilterAreasSpatialRS computeAreaFilter(final FilterAreasSpatialRQ request) throws ServiceException {

        final UserAreasType userAreas = request.getUserAreas();
        final ScopeAreasType scopeAreas = request.getScopeAreas();
        final StringBuilder sb = new StringBuilder();
        final List<AreaLocationTypesEntity> typesEntities = repository.findAllIsLocation(false);
        final Map<String, AreaLocationTypesEntity> typesEntityMap = new HashMap<>();

        for (AreaLocationTypesEntity typesEntity : typesEntities) {
            typesEntityMap.put(typesEntity.getTypeName(), typesEntity);
        }

        buildQuery(scopeAreas.getScopeAreas(), sb, "scope", typesEntityMap);

        if (userAreas != null) {
            if (CollectionUtils.isNotEmpty(userAreas.getUserAreas()) && StringUtils.isNotEmpty(sb.toString())) {
                sb.append(" UNION ALL ");
            }
            buildQuery(userAreas.getUserAreas(), sb, "user", typesEntityMap);
        }

        log.debug("{} QUERY => {}", sb.toString());

        List records = repository.listBaseAreaList(sb.toString());

        final List<Geometry> scopeGeometryList = new ArrayList<>();
        final List<Geometry> userGeometryList = new ArrayList<>();

        for (Object record : records) {
            final Object[] result = (Object[]) record;
            final String type = (String) result[0];
            Geometry geometry = (Geometry) result[1];

            Integer epsgSRID = repository.mapToEpsgSRID(geometry.getSRID());

            if (!isDefaultEpsgSRID(epsgSRID)) {
                geometry = GeometryUtils.toGeographic(geometry, epsgSRID);
            }

            if ("user".equals(type)) {
                userGeometryList.add(geometry);
            } else {
                scopeGeometryList.add(geometry);
            }
        }

        GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();

        Geometry userUnion = geometryFactory.buildGeometry(userGeometryList).union();
        Geometry scopeUnion = geometryFactory.buildGeometry(scopeGeometryList).union();

        FilterAreasSpatialRS response = new FilterAreasSpatialRS(null, 0);
        Geometry intersection = null;

        if (!userUnion.isEmpty() && !scopeUnion.isEmpty()) {
            intersection = userUnion.intersection(scopeUnion);
            response.setCode(3);
            if (intersection.isEmpty()) {
                intersection = scopeUnion;
                response.setCode(4);
            }
        } else if (!userUnion.isEmpty()) {
            intersection = userUnion;
            response.setCode(1);
        } else if (!scopeUnion.isEmpty()) {
            intersection = scopeUnion;
            response.setCode(2);
        }

        if (intersection != null) {
            if (intersection.getNumPoints() > 20000) {
                intersection = DouglasPeuckerSimplifier.simplify(intersection, 0.5);
            }
            response.setGeometry(GeometryMapper.INSTANCE.geometryToWkt(intersection).getValue());
        }

        return response;
    }

    private void buildQuery(List<AreaIdentifierType> typeList, StringBuilder sb, String type,
            Map<String, AreaLocationTypesEntity> typesEntityMap) {

        Iterator<AreaIdentifierType> it = typeList.iterator();

        while (it.hasNext()) {
            AreaIdentifierType next = it.next();
            final String id = next.getId();
            final AreaType areaType = next.getAreaType();
            final AreaLocationTypesEntity locationTypesEntity = typesEntityMap.get(areaType.value());
            sb.append("(SELECT '").append(type).append("' as type ,geom FROM spatial.")
                    .append(locationTypesEntity.getAreaDbTable()).append(" spatial WHERE spatial.gid = ").append(id)
                    .append(" AND enabled = 'Y')");
            it.remove(); // avoids a ConcurrentModificationException
            if (it.hasNext()) {
                sb.append(" UNION ALL ");
            }
        }
    }

    @Override
    @Interceptors(ValidationInterceptor.class)
    public List<GenericSystemAreaDto> searchAreasByNameOrCode(@NotNull String areaType, @NotNull String filter)
            throws ServiceException {

        AreaLocationTypesEntity areaLocationType = repository
                .findAreaLocationTypeByTypeName(areaType.toUpperCase());

        final ArrayList<GenericSystemAreaDto> systemAreaByFilterRecords = new ArrayList<>();
        //FIXME use generic dao
        List<BaseAreaEntity> baseEntities = DAOFactory.getAbstractSpatialDao(em, areaLocationType.getTypeName())
                .searchEntity(filter);
        for (BaseAreaEntity entity : baseEntities) {
            Geometry geometry = entity.getGeom();
            Geometry envelope = geometry.getGeometryType().equalsIgnoreCase(MULTIPOINT) ? geometry
                    : entity.getGeom().getEnvelope();
            systemAreaByFilterRecords.add(
                    new GenericSystemAreaDto(entity.getId().intValue(), entity.getCode(), areaType.toUpperCase(),
                            GeometryMapper.INSTANCE.geometryToWkt(envelope).getValue(), entity.getName()));
        }

        return systemAreaByFilterRecords;

    }

    @Override
    public Map<String, Object> getLocationDetails(final LocationTypeEntry locationTypeEntry)
            throws ServiceException {

        BaseAreaEntity match = null;

        GeodeticCalculator calc = new GeodeticCalculator(GeometryUtils.toDefaultCoordinateReferenceSystem());
        String id = locationTypeEntry.getId();
        String locationType = locationTypeEntry.getLocationType();
        AreaLocationTypesEntity locationTypesEntity;
        Double incomingLatitude = locationTypeEntry.getLatitude();
        Double incomingLongitude = locationTypeEntry.getLongitude();

        if (id != null && !StringUtils.isNumeric(id)) {
            throw new SpatialServiceException(SpatialServiceErrors.INVALID_ID_TYPE, id);
        }

        locationTypesEntity = repository.findAreaLocationTypeByTypeName(locationType.toUpperCase());

        if (locationTypesEntity == null) {
            throw new ServiceException("TYPE CANNOT BE NULL");
        } else if (!locationTypesEntity.getIsLocation()) {
            throw new ServiceException(locationTypesEntity.getTypeName() + " IS NOT A LOCATION");
        }

        if (locationTypeEntry.getId() != null) {
            AbstractAreaDao dao = DAOFactory.getAbstractSpatialDao(em, locationTypesEntity.getTypeName());
            BaseAreaEntity areaEntity = dao.findOne(Long.parseLong(locationTypeEntry.getId()));
            if (areaEntity == null) {
                throw new SpatialServiceException(SpatialServiceErrors.ENTITY_NOT_FOUND,
                        locationTypesEntity.getTypeName());
            }
            match = areaEntity;
        } else {

            Point point = (Point) GeometryUtils.toGeographic(incomingLatitude, incomingLongitude,
                    locationTypeEntry.getCrs());

            List<PortEntity> records = repository.listClosestPorts(point, 5);
            Double closestDistance = Double.MAX_VALUE;

            for (PortEntity portsEntity : records) {

                final Geometry geometry = portsEntity.getGeom();
                final Point centroid = geometry.getCentroid();
                calc.setStartingGeographicPoint(centroid.getX(), centroid.getY());
                calc.setDestinationGeographicPoint(point.getX(), point.getY());
                Double orthodromicDistance = calc.getOrthodromicDistance();

                if (closestDistance > orthodromicDistance) {
                    closestDistance = orthodromicDistance;
                    match = portsEntity;
                }
            }
        }
        ObjectMapper objectMapper = new ObjectMapper();
        Map map = objectMapper.convertValue(match, Map.class);
        return map;
    }

    @Override
    public List<SystemAreaNamesDto> searchAreasByCode(String areaType, String filter) throws ServiceException {
        AreaLocationTypesEntity areaLocationType = repository
                .findAreaLocationTypeByTypeName(areaType.toUpperCase());

        if (areaLocationType == null) {
            throw new SpatialServiceException(SpatialServiceErrors.INTERNAL_APPLICATION_ERROR);
        }
        List<BaseAreaEntity> baseEntities = DAOFactory.getAbstractSpatialDao(em, areaLocationType.getTypeName())
                .searchNameByCode(filter);

        List<SystemAreaNamesDto> systemAreas = new ArrayList<>();
        for (BaseAreaEntity baseEntity : baseEntities) {
            boolean isAdded = false;
            for (SystemAreaNamesDto systemAreaNamesDto : systemAreas) {
                if (systemAreaNamesDto.getCode().equalsIgnoreCase(baseEntity.getCode())) {
                    systemAreaNamesDto.getAreaNames().add(baseEntity.getName());
                    List<Geometry> geometries = systemAreaNamesDto.getGeoms();
                    Geometry geometry = baseEntity.getGeom();
                    geometries.add(geometry);
                    isAdded = true;
                }
            }
            if (!isAdded) {
                systemAreas.add(new SystemAreaNamesDto(baseEntity.getCode(),
                        new HashSet<>(Arrays.asList(baseEntity.getName())),
                        new ArrayList(Arrays.asList(baseEntity.getGeom()))));
            }
        }

        for (SystemAreaNamesDto systemAreaNamesDto : systemAreas) {
            GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
            Geometry unionGeom = geometryFactory.buildGeometry(systemAreaNamesDto.getGeoms()).union();
            systemAreaNamesDto.setExtent(GeometryMapper.INSTANCE.geometryToWkt(unionGeom.getEnvelope()).getValue());
        }

        return systemAreas;
    }

    @Override
    @SneakyThrows
    @Interceptors(ValidationInterceptor.class)
    public List<Map<String, Object>> getAreasByPoint(@NotNull Double latitude, @NotNull Double longitude,
            @NotNull Integer crs, @NotNull String userName, @NotNull AreaType areaType) throws ServiceException {

        AreaLocationTypesEntity areaLocationTypesEntity = repository
                .findAreaLocationTypeByTypeName(areaType.value().toUpperCase());

        Point point = (Point) GeometryUtils.toGeographic(latitude, longitude, crs);

        // FIXME try using ethod of repo remove DAOFactory stuff
        List byIntersect = DAOFactory.getAbstractSpatialDao(em, areaLocationTypesEntity.getTypeName())
                .findByIntersect(point);

        ObjectMapper objectMapper = new ObjectMapper();
        List<Map<String, Object>> objectAsMap = new ArrayList<>();

        for (Object o : byIntersect) {
            objectAsMap.add(objectMapper.convertValue(o, Map.class));
        }

        return objectAsMap;
    }

    @Override // FIXME is kind off a duplicate of List<Map<String, Object>> getAreasByPoint
    public List<AreaExtendedIdentifierType> getAreasByPoint(final AreaByLocationSpatialRQ request)
            throws ServiceException {
        if (request == null) {
            throw new ServiceException("REQUEST CAN NOT BE NULL");
        }
        if (request.getPoint() == null) {
            throw new ServiceException("POINT CAN NOT BE NULL");
        }
        Double lat = request.getPoint().getLatitude();
        Double lon = request.getPoint().getLongitude();
        Integer crs = request.getPoint().getCrs();

        if (lat == null || lon == null || crs == null) {
            throw new ServiceException("MISSING MANDATORY FIELDS");
        }

        final Point incoming = (Point) GeometryUtils.toGeographic(lat, lon, crs);
        final List<AreaLocationTypesEntity> typesEntities = repository.findByIsLocationAndIsSystemWide(false, true);
        final List<AreaExtendedIdentifierType> areaTypes = new ArrayList<>();

        List records = repository.intersectingArea(typesEntities, databaseDialect, incoming);

        for (Object record : records) {
            final Object[] result = (Object[]) record;
            AreaExtendedIdentifierType area = new AreaExtendedIdentifierType();
            area.setAreaType(AreaType.valueOf(String.valueOf(result[0])));
            area.setId(String.valueOf(result[1]));
            area.setCode(String.valueOf(result[2]));
            area.setName(String.valueOf(result[3]));
            areaTypes.add(area);
        }

        return areaTypes;
    }

    @Override
    public String getGeometryForPort(String portCode) {
        if (portCode == null) {
            return null;
        }
        log.debug("Port code received in getGeometryForPort :" + portCode);
        String geomWkt = null;
        try {
            List<BaseAreaEntity> baseEntities = DAOFactory.getAbstractSpatialDao(em, "port")
                    .searchNameByCode(portCode);

            if (CollectionUtils.isNotEmpty(baseEntities)) {
                for (BaseAreaEntity baseAreaEntity : baseEntities) {
                    if (baseAreaEntity.getGeom() != null) {
                        Geometry geometry = baseAreaEntity.getGeom();
                        geomWkt = GeometryMapper.INSTANCE.geometryToWkt(geometry).getValue();
                        log.debug("geomWkt received :" + geomWkt);
                        break;
                    }
                }
            }
        } catch (ServiceException e) {
            log.error("Could not fetch geometry for the portCode:" + portCode, e);
        }

        return geomWkt;
    }

}