/**
* OrbisGIS is a java GIS application dedicated to research in GIScience.
* OrbisGIS is developed by the GIS group of the DECIDE team of the
* Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>.
*
* The GIS group of the DECIDE team is located at :
*
* Laboratoire Lab-STICC – CNRS UMR 6285
* Equipe DECIDE
* UNIVERSITÉ DE BRETAGNE-SUD
* Institut Universitaire de Technologie de Vannes
* 8, Rue Montaigne - BP 561 56017 Vannes Cedex
*
* OrbisGIS is distributed under GPL 3 license.
*
* Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488)
* Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285)
*
* This file is part of OrbisGIS.
*
* OrbisGIS 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 (at your option) any later
* version.
*
* OrbisGIS 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
* OrbisGIS. If not, see <http://www.gnu.org/licenses/>.
*
* For more information, please consult: <http://www.orbisgis.org/>
* or contact directly:
* info_at_ orbisgis.org
*/
package org.orbisgis.coremap.layerModel;
import com.vividsolutions.jts.geom.Envelope;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import net.opengis.ows._2.BoundingBoxType;
import net.opengis.ows._2.LanguageStringType;
import net.opengis.ows_context.LayerType;
import net.opengis.ows_context.OWSContextType;
import net.opengis.ows_context.ObjectFactory;
import net.opengis.ows_context.OnlineResourceType;
import net.opengis.ows_context.ResourceListType;
import net.opengis.ows_context.SLDType;
import net.opengis.ows_context.StyleListType;
import net.opengis.ows_context.StyleType;
import net.opengis.ows_context.URLType;
import org.slf4j.*;
import org.h2gis.utilities.TableLocation;
import org.orbisgis.corejdbc.DataManager;
import org.orbisgis.coremap.map.MapTransform;
import org.orbisgis.coremap.renderer.ImageRenderer;
import org.orbisgis.coremap.renderer.Renderer;
import org.orbisgis.coremap.renderer.se.SeExceptions.InvalidStyle;
import org.orbisgis.coremap.renderer.se.Style;
import org.orbisgis.coremap.renderer.se.common.Description;
import org.orbisgis.coremap.renderer.se.common.LocalizedText;
import org.orbisgis.commons.progress.NullProgressMonitor;
import org.orbisgis.commons.progress.ProgressMonitor;
import org.h2gis.utilities.SFSUtilities;
import org.h2gis.utilities.URIUtilities;
import org.orbisgis.commons.utils.FileUtils;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;
/**
* Class that contains the status of the map view.
*
*
*
*/
public final class OwsMapContext extends BeanMapContext {
private static final I18n I18N = I18nFactory.getI18n(OwsMapContext.class, Locale.getDefault(), I18nFactory.FALLBACK);
private static final Logger LOGGER = LoggerFactory.getLogger(OwsMapContext.class);
private ArrayList<MapContextListener> listeners = new ArrayList<MapContextListener>();
private OpenerListener openerListener;
private boolean open = false;
private OWSContextType jaxbMapContext = null; //Persistent form of the MapContext
private long idTime;
private DataManager dataManager;
/**
* Default constructor
*/
public OwsMapContext(DataManager dataManager) {
openerListener = new OpenerListener();
setRootLayer(createLayerCollection("root"));
//Create an empty map context
jaxbMapContext = createJaxbMapContext();
idTime = System.currentTimeMillis();
this.dataManager = dataManager;
}
@Override
public DataManager getDataManager() {
return dataManager;
}
@Override
public ILayer createLayer(String layerName, String tableRef) throws LayerException {
try {
try (Connection connection = dataManager.getDataSource().getConnection()) {
List<String> geoFields = SFSUtilities.getGeometryFields(connection, TableLocation.parse(tableRef));
if (!geoFields.isEmpty()) {
return new Layer(layerName, tableRef, dataManager);
} else {
throw new LayerException(I18N.tr("The source contains no spatial info"));
}
}
} catch (SQLException ex) {
throw new LayerException("Cannot retrieve spatial metadata",ex);
}
}
@Override
public ILayer createLayer(String tableRef) throws LayerException {
return createLayer(TableLocation.parse(tableRef).getTable(), tableRef);
}
@Override
public ILayer createLayer(String layerName, URI source) throws LayerException {
return new Layer(layerName, source, dataManager);
}
@Override
public ILayer createLayer(URI source) throws LayerException {
if(!source.isAbsolute()) {
// If URI is not absolute ex URI.create("../folder/myfile.shp"), then create a canonical URI
try {
source = new File(location != null ? new File(location) : new File("./"),
source.toString()).getCanonicalFile().toURI();
} catch (IOException ex) {
throw new LayerException(ex);
}
}
String layerName;
try {
layerName = FileUtils.getNameFromURI(source);
} catch (UnsupportedOperationException ex) {
try {
layerName = dataManager.findUniqueTableName(I18N.tr("Layer"));
} catch (SQLException ex2) {
throw new LayerException(ex2);
}
}
return createLayer(layerName, source);
}
@Override
public ILayer createLayerCollection(String layerName) {
return new LayerCollection(layerName);
}
private void setRootLayer(ILayer newRoot) {
if (layerModel != null) {
layerModel.removeLayerListenerRecursively(openerListener);
}
super.setLayerModel(newRoot);
layerModel.addLayerListenerRecursively(openerListener);
}
@Override
protected void setLayerModel(ILayer newRoot) {
setRootLayer(newRoot);
}
@Override
public void addMapContextListener(MapContextListener listener) {
listeners.add(listener);
}
@Override
public void removeMapContextListener(MapContextListener listener) {
listeners.remove(listener);
}
@Override
public ILayer getLayerModel() {
checkIsOpen();
return super.getLayerModel();
}
@Override
public long getIdTime() {
return idTime;
}
@Override
public ILayer[] getLayers() {
checkIsOpen();
return getLayerModel().getLayersRecursively();
}
@Override
public Style[] getSelectedStyles() {
checkIsOpen();
return super.getSelectedStyles();
}
@Override
public ILayer[] getSelectedLayers() {
checkIsOpen();
return super.getSelectedLayers();
}
@Override
public void setSelectedStyles(Style[] selectedStyles) {
checkIsOpen();
super.setSelectedStyles(selectedStyles);
//DEPRECATED LISTENERS
for (MapContextListener listener : listeners) {
listener.layerSelectionChanged(this);
}
}
@Override
public void setSelectedLayers(ILayer[] selectedLayers) {
checkIsOpen();
ArrayList<ILayer> filtered = new ArrayList<ILayer>();
for (ILayer layer : selectedLayers) {
if (layerModel.getLayerByName(layer.getName()) != null) {
filtered.add(layer);
}
}
super.setSelectedLayers(filtered.toArray(new ILayer[filtered.size()]));
//DEPRECATED LISTENERS
for (MapContextListener listener : listeners) {
listener.layerSelectionChanged(this);
}
}
private final class OpenerListener extends LayerListenerAdapter {
@Override
public void layerAdded(LayerCollectionEvent e) {
if (isOpen()) {
for (final ILayer layer : e.getAffected()) {
try {
layer.open();
layer.addLayerListenerRecursively(openerListener);
// checking & possibly setting SRID
// checkSRID(layer);
} catch (LayerException ex) {
LOGGER.error(
I18N.tr("Cannot open layer : {0} ", layer.getName()), ex);
try {
layer.getParent().remove(layer);
} catch (LayerException e1) {
LOGGER.error(
I18N.tr("Cannot remove the layer {0}", layer.getName()), ex);
}
}
}
}
}
@Override
public void layerRemoved(LayerCollectionEvent e) {
HashSet<ILayer> newSelection = new HashSet<ILayer>();
newSelection.addAll(Arrays.asList(selectedLayers));
ILayer[] affected = e.getAffected();
for (final ILayer layer : affected) {
// Check active
if (activeLayer == layer) {
setActiveLayer(null);
}
// Check selection
newSelection.remove(layer);
layer.removeLayerListenerRecursively(openerListener);
if (isOpen()) {
try {
layer.close();
} catch (LayerException e1) {
LOGGER.warn(I18N.tr("Cannot close layer {0}", layer.getName()), e1);
}
}
}
setSelectedLayers(newSelection.toArray(new ILayer[newSelection.size()]));
// checkIfHasToResetSRID();
}
}
@Override
public boolean isLayerModelSpatial(){
ILayer[] layers = getLayers();
for(ILayer l : layers){
if(!l.acceptsChilds()){
return true;
}
}
return false;
}
/**
* Implementation of public draw method
*
* @param mt Contain the extent and the image to draw on
* @param pm Object to report process and check the cancelled condition
* @param layer Draw recursively this layer
* @throws IllegalStateException If the map is closed
*/
private void drawImpl(MapTransform mt, ProgressMonitor pm, ILayer layer) throws IllegalStateException {
checkIsOpen();
Renderer renderer = new ImageRenderer();
renderer.draw(mt, layer, pm);
}
@Override
public void draw(MapTransform mt, ProgressMonitor pm, ILayer layer) {
//Layer must be from this layer model
if (!isLayerFromThisLayerModel(layer)) {
throw new IllegalStateException(I18N.tr("Layer provided for drawing is not from the map context layer model."));
}
drawImpl(mt, pm, layer);
}
@Override
public void draw(MapTransform mt,
ProgressMonitor pm) {
drawImpl(mt, pm, getLayerModel());
}
/**
* Search recursively for the specified layer in the layer model
*
* @param layer Searched layer
* @return True if the layer is in the map context layer model
*/
private boolean isLayerFromThisLayerModel(ILayer layer) {
ILayer[] allLayers = getLayerModel().getLayersRecursively();
return Arrays.asList(allLayers).contains(layer);
}
private void checkIsOpen() {
if (!isOpen()) {
throw new IllegalStateException(
I18N.tr("The map is not open")); //$NON-NLS-1$
}
}
@Override
public ILayer getActiveLayer() {
checkIsOpen();
return activeLayer;
}
@Override
public void setActiveLayer(ILayer activeLayer) {
checkIsOpen();
ILayer lastActive = this.activeLayer;
this.activeLayer = activeLayer;
propertyChangeSupport.firePropertyChange(PROP_ACTIVELAYER, lastActive, activeLayer);
}
private OWSContextType createJaxbMapContext() {
//Create jaxbcontext data
ObjectFactory ows_context_factory = new ObjectFactory();
net.opengis.ows._2.ObjectFactory ows_factory = new net.opengis.ows._2.ObjectFactory();
OWSContextType mapContextSerialisation = ows_context_factory.createOWSContextType();
//GeneralType
mapContextSerialisation.setGeneral(ows_context_factory.createGeneralType());
//GeneralType:Bounding Box
if (boundingBox != null) {
BoundingBoxType bbox = ows_factory.createBoundingBoxType();
bbox.getLowerCorner().add(boundingBox.getMinX());
bbox.getLowerCorner().add(boundingBox.getMinY());
bbox.getUpperCorner().add(boundingBox.getMaxX());
bbox.getUpperCorner().add(boundingBox.getMaxY());
mapContextSerialisation.getGeneral().setBoundingBox(ows_factory.createBoundingBox(bbox));
}
//GeneralType:Title
Map<Locale,String> titles = description.getTitles();
if(!titles.isEmpty()) {
//Take the first one
for(Entry<Locale,String> entry : titles.entrySet()) {
LanguageStringType title = ows_factory.createLanguageStringType();
title.setLang(LocalizedText.toLanguageTag(entry.getKey()));
title.setValue(entry.getValue());
mapContextSerialisation.getGeneral().setTitle(title);
break; //Ows-context does not support multi-lingual
}
}
//GeneralType:Abstract
Map<Locale,String> abstracts = description.getAbstractTexts();
if(!abstracts.isEmpty()) {
//Take the first one
for(Entry<Locale,String> entry : abstracts.entrySet()) {
LanguageStringType mapAbstract = ows_factory.createLanguageStringType();
mapAbstract.setLang(LocalizedText.toLanguageTag(entry.getKey()));
mapAbstract.setValue(entry.getValue());
mapContextSerialisation.getGeneral().setAbstract(mapAbstract);
break; //Ows-context does not support multi-lingual
}
}
//ResourceList
ResourceListType rs = ows_context_factory.createResourceListType();
mapContextSerialisation.setResourceList(rs);
//LayerList
if (layerModel != null) {
List<LayerType> rootLayerList = rs.getLayer();
ILayer[] rootLayers = layerModel.getChildren();
for (ILayer layer : rootLayers) {
if(layer.isSerializable()){
rootLayerList.add(createJAXBFromLayer(layer, this));
}
}
}
return mapContextSerialisation;
}
private static LayerType createJAXBFromLayer(ILayer layer, MapContext mapContext) {
ObjectFactory ows_context_factory = new ObjectFactory();
LayerType layerType = ows_context_factory.createLayerType();
Description description = layer.getDescription();
description.initJAXBType(layerType);
layerType.setHidden(!layer.isVisible());
ILayer[] childrens = layer.getChildren();
for(ILayer child : childrens) {
if(child.isSerializable()){
layerType.getLayer().add(createJAXBFromLayer(child, mapContext));
}
}
// If not a Layer Collection
if(!(layer instanceof LayerCollection) && layer.getStyles()!=null) {
StyleListType slt = ows_context_factory.createStyleListType();
layerType.setStyleList(slt);
for(Style style : layer.getStyles()) {
StyleType st = ows_context_factory.createStyleType();
slt.getStyle().add(st);
SLDType sltType = ows_context_factory.createSLDType();
st.setSLD(sltType);
sltType.setAbstractStyle(style.getJAXBElement());
}
}
//Serialisation of dataSource as a DataUrl string
//Create jaxb instances
URLType dataURL = ows_context_factory.createURLType();
if(!(layer instanceof LayerCollection)) {
OnlineResourceType resource = ows_context_factory.createOnlineResourceType();
dataURL.setOnlineResource(resource);
String resourceSerialisation = "";
URI srcUri = layer.getDataUri();
if(srcUri!=null) {
// If file, use MapContext relative path
if(srcUri.getScheme().equalsIgnoreCase("file") && mapContext.getLocation() != null) {
srcUri = URIUtilities.relativize(mapContext.getLocation(), srcUri);
}
resourceSerialisation = srcUri.toString();
}
resource.setHref(resourceSerialisation);
if(resource.isSetHref()) {
layerType.setDataURL(dataURL);
}
}
return layerType;
}
@Override
public void read(InputStream in) throws IllegalArgumentException {
try {
Unmarshaller unMarsh = org.orbisgis.coremap.map.JaxbContainer.JAXBCONTEXT.createUnmarshaller();
JAXBElement<OWSContextType> importedOwsContextType = (JAXBElement<OWSContextType>) unMarsh.unmarshal(in);
setJAXBObject(importedOwsContextType.getValue());
} catch (JAXBException ex) {
throw new IllegalArgumentException(I18N.tr("Unable to read the provided map context"), ex);
}
}
@Override
public void write(OutputStream out) {
ObjectFactory owsFactory = new ObjectFactory();
try {
JAXBElement<OWSContextType> exportedOwsContextType = owsFactory.createOWSContext(getJAXBObject());
Marshaller marshaller = org.orbisgis.coremap.map.JaxbContainer.JAXBCONTEXT.createMarshaller();
marshaller.setProperty("jaxb.formatted.output", Boolean.TRUE);
marshaller.marshal(exportedOwsContextType, out);
} catch (JAXBException ex) {
throw new IllegalArgumentException(I18N.tr("Error raised while exporting the map context"), ex);
}
}
/**
*
* @return The internal serialisation objects of the Map Context
*/
public OWSContextType getJAXBObject() {
if (jaxbMapContext == null) {
return createJaxbMapContext();
}
return jaxbMapContext;
}
private void setJAXBObject(OWSContextType jaxbObject) {
if (isOpen()) {
throw new IllegalStateException(
I18N.tr("The map must be closed to invoke this method")); //$NON-NLS-1$
}
this.jaxbMapContext = (OWSContextType) jaxbObject;
}
@Override
public void close(ProgressMonitor pm) {
checkIsOpen();
//Backup Consistent data
jaxbMapContext = getJAXBObject();
// Close the layers
if (pm == null) {
pm = new NullProgressMonitor();
}
ILayer[] layers = layerModel.getLayersRecursively();
for (int i = 0; i < layers.length; i++) {
pm.progressTo(i * 100 / layers.length);
if (!layers[i].acceptsChilds()) {
try {
layers[i].close();
} catch (LayerException e) {
LOGGER.error(I18N.tr("Cannot close layer {0}", layers[i].getName()));
}
}
}
layerModel.removeLayerListenerRecursively(openerListener);
this.open = false;
}
/**
* Recursive function to parse a layer tree
*
* @param lt
* @param parentLayer
*/
private void parseJaxbLayer(LayerType lt, ILayer parentLayer) throws LayerException {
//Test if lt is a group
if(!lt.getLayer().isEmpty() || (lt.getDataURL()==null )) {
//it will create a LayerCollection
parseJaxbLayerCollection(lt, parentLayer);
} else {
//it corresponding to a leaf layer
//We need to read the data declaration and create the layer
URLType dataUrl = lt.getDataURL();
if (dataUrl != null) {
OnlineResourceType resType = dataUrl.getOnlineResource();
try {
URI layerURI = new URI(resType.getHref());
// The resource is given as relative to MapContext location
if(!layerURI.isAbsolute() && getLocation()!=null) {
try {
// Resolve the relative resource ex: new Uri("myFile.shp")
layerURI = getLocation().resolve(layerURI);
} catch (IllegalArgumentException ex) {
LOGGER.warn("Error while trying to find an absolute path for an external resource", ex);
}
}
//Get table name
ILayer leafLayer = createLayer(layerURI);
leafLayer.setDescription(new Description(lt));
leafLayer.setVisible(!lt.isHidden());
//Parse styles
if(lt.isSetStyleList()) {
for(StyleType st : lt.getStyleList().getStyle()) {
if(st.isSetSLD()) {
if(st.getSLD().isSetAbstractStyle()) {
leafLayer.addStyle(new Style((JAXBElement<net.opengis.se._2_0.core.StyleType>)st.getSLD().getAbstractStyle(), leafLayer));
}
}
}
}
parentLayer.addLayer(leafLayer);
} catch (URISyntaxException ex) {
throw new LayerException(I18N.tr("Unable to parse the href URI {0}.", resType.getHref()), ex);
} catch (InvalidStyle ex) {
throw new LayerException(I18N.tr("Unable to load the description of the layer {0}", lt.getTitle().toString()), ex);
}
}
}
}
/**
* Recursive function to parse a layer tree
*
* @param lt
* @param parentLayer
*/
private void parseJaxbLayerCollection(LayerType lt, ILayer parentLayer) throws LayerException {
LayerCollection layerCollection = new LayerCollection(lt);
for (LayerType ltc : lt.getLayer()) {
try {
parseJaxbLayer(ltc, layerCollection);
} catch (LayerException ex) {
//The layer is not created if a layer exception is thrown
//Create a warning, because the MapContext is loaded
LOGGER.warn(I18N.tr("The layer has not been imported"), ex);
}
}
parentLayer.addLayer(layerCollection);
}
private void loadOwsContext() throws LayerException {
if (jaxbMapContext != null) {
//Load bounding box
if (jaxbMapContext.getGeneral().getBoundingBox() != null) {
List<Double> lowerCorner = jaxbMapContext.getGeneral().getBoundingBox().getValue().getLowerCorner();
List<Double> upperCorner = jaxbMapContext.getGeneral().getBoundingBox().getValue().getUpperCorner();
if (lowerCorner.size() >= 2 && upperCorner.size() >= 2) {
setBoundingBox(new Envelope(lowerCorner.get(0), upperCorner.get(0), lowerCorner.get(1), upperCorner.get(1)));
}
}
//Load title
Description nextDescription = new Description();
if(jaxbMapContext.getGeneral().getTitle() != null) {
LanguageStringType title = jaxbMapContext.getGeneral().getTitle();
Locale locale;
if(title.getLang()!=null) {
locale = LocalizedText.forLanguageTag(title.getLang());
} else {
locale = Locale.getDefault();
}
nextDescription.addTitle(locale,title.getValue());
}
//Load abstract
if(jaxbMapContext.getGeneral().getAbstract() != null) {
LanguageStringType mapAbstract = jaxbMapContext.getGeneral().getAbstract();
Locale locale;
if(mapAbstract.getLang()!=null) {
locale = LocalizedText.forLanguageTag(mapAbstract.getLang());
} else {
locale = Locale.getDefault();
}
nextDescription.addAbstract(locale,mapAbstract.getValue());
}
setDescription(nextDescription);
//Collect DataSource URI already loaded
//Load layers and DataSource
//Root layer correspond to ResourceList
setRootLayer(createLayerCollection("root"));
for (LayerType lt : jaxbMapContext.getResourceList().getLayer()) {
try {
parseJaxbLayer(lt, getLayerModel());
} catch (LayerException ex) {
//The layer is not created if a layer exception is thrown
LOGGER.error(I18N.tr("The layer has not been imported"), ex);
}
}
}
}
@Override
public void open(ProgressMonitor pm) throws LayerException {
if (isOpen()) {
throw new IllegalStateException(
I18N.tr("The map is already open"));
}
open = true;
this.activeLayer = null;
//Read the specified jaxbMapContext
setSelectedLayers(new ILayer[0]);
setSelectedStyles(new Style[0]);
loadOwsContext();
jaxbMapContext = null;
}
@Override
public boolean isOpen() {
return open;
}
/*
* A mapcontext must have only one {@link CoordinateReferenceSystem} By
* default the crs is set to null.
*/
/*
* public CoordinateReferenceSystem getCoordinateReferenceSystem() {
* return crs; }
*
* /** Set a {@link CoordinateReferenceSystem} to the mapContext
*
* @param crs
*//*
* public void setCoordinateReferenceSystem(CoordinateReferenceSystem
* crs) { this.crs = crs; }
*/
}