// Copyright (C) Stichting Deltares 2018. All rights reserved. // // This file is part of the application DAM - Clients Library. // // DAM - UI 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. // // This program 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 this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.Linq; using GeoAPI.Geometries; using NetTopologySuite.Features; using NetTopologySuite.Index.Quadtree; namespace Deltares.Maps { /// /// Spatial Indexed Feature Repository /// public class FeatureRepository : IFeatureRepository { private readonly IList emptyGeometryFeatures; private readonly IList features; private Quadtree spatialIndex; public FeatureRepository() { features = new List(); emptyGeometryFeatures = new List(); spatialIndex = new Quadtree(); } public static IFeatureRepository CreateFromShapeFile(string fileName) { if (fileName == null) { throw new ArgumentNullException("fileName"); } return CreateFromShapeFile(new ShapeFileLocation(fileName)); } public IEnumerable Query(IFeature feature) { return Query(feature.Geometry); } public IEnumerable Query(IGeometry geometry) { return geometry.IsEmpty ? emptyGeometryFeatures : Query(geometry.EnvelopeInternal); } /// /// Gets features based on the envelope. /// /// /// Note that this call won't handle empty geometries. To return empty geometries as well use Query(IFeature) or /// Query(IGeometry) /// instead of this call /// /// /// public IEnumerable Query(Envelope envelope) { //return this.featureLookup.Values.Where(f => envelope.Intersects(f.Geometry.EnvelopeInternal)); return spatialIndex.Query((Envelope)envelope); } public static IFeatureRepository CreateFromShapeFile(ShapeFileLocation shapeFileLocation) { if (shapeFileLocation == null) { throw new ArgumentNullException("shapeFileLocation"); } var repository = new FeatureRepository(); try { FeatureImporter.ImportFromShapeFile(shapeFileLocation, repository); } catch (Exception e) { throw e; } return repository; } public IEnumerable FeatureIds { get { return Features.Select(geom => geom.Id); } } public IGeometry GetGeometryById(Guid id) { //var feature = this.featureLookup.Values.FirstOrDefault(item => item.Id == id); IFeature feature = Features.FirstOrDefault(item => item.Id == id); if (feature != null) { return feature.Geometry; } throw new ArgumentException(string.Format("Unknown id '{0}', geometry not found", id)); } public IEnumerable> GetAttributes(IGeometry geom) { if (geom == null) { throw new ArgumentNullException("geom"); } IFeature feature = Features.SingleOrDefault( f => f.Geometry == geom || f.Geometry.AsText().GetHashCode() == geom.AsText().GetHashCode()); if (feature != null) { IAttributesTable attributes = feature.Attributes; return attributes.GetNames().Select(attr => new KeyValuePair(attr, attributes[attr])); ; } throw new ArgumentException("Couldn't find the geometry in the repository"); } public IEnumerable Features { get { return features.Concat(emptyGeometryFeatures); } } /// /// TODO: Is it allowed to have different attributes per geometry? If this is the case the we have to create a fast and /// robust attribute collector. /// public IEnumerable SupportedAttributes { get { if (Features == null || !Features.Any()) { return new List(); } return Features.SelectMany(geometry => geometry.Attributes.GetNames()).Distinct(); } } public void Add(IFeature feature) { if (feature == null) { throw new ArgumentNullException("feature"); } if (feature.Geometry.IsEmpty) { emptyGeometryFeatures.Add(feature); } else { features.Add(feature); AddToSpatialIndex(feature); } } private void AddToSpatialIndex(IFeature feature) { try { spatialIndex.Insert(feature.Geometry.EnvelopeInternal, feature); } catch (Exception e) { throw e; } } private void RemoveFromSpatialIndex(IFeature feature) { spatialIndex.Insert(feature.Geometry.EnvelopeInternal, feature); } public void Add(IEnumerable featureCollection) { if (featureCollection == null) { throw new ArgumentNullException("featureCollection"); } foreach (IFeature feature in featureCollection) { Add(feature); } } public void Remove(IFeature feature) { if (feature == null) { throw new ArgumentNullException("feature"); } if (feature.Geometry.IsEmpty) { emptyGeometryFeatures.Remove(feature); } else { features.Remove(feature); RemoveFromSpatialIndex(feature); } } public void Remove(Func predicate) { if (predicate == null) { throw new ArgumentNullException("predicate"); } var work = new Queue(Count); foreach (IFeature feature in Features.Where(predicate)) { work.Enqueue(feature); } while (work.Count > 0) { Remove(work.Dequeue()); } } public void Add(Feature feature, Func predicate) { if (feature == null) { throw new ArgumentNullException("feature"); } if (predicate == null) { throw new ArgumentNullException("predicate"); } if (predicate(feature)) { Add(feature); } } /// /// Clears the list of features in the repository /// public void Clear() { features.Clear(); emptyGeometryFeatures.Clear(); spatialIndex = new Quadtree(); } /// /// Gets the feature count in the repository /// public int Count { get { return features.Count + emptyGeometryFeatures.Count; } } } }