Skip to content

Commit

Permalink
Geo URI strings can now be used in queries
Browse files Browse the repository at this point in the history
  • Loading branch information
sibartlett committed Mar 16, 2013
1 parent 26a030e commit 92792ca
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 175 deletions.
10 changes: 10 additions & 0 deletions Raven.Abstractions/Spatial/ShapeConverter.cs
Expand Up @@ -10,6 +10,9 @@

namespace Raven.Abstractions.Spatial
{
/// <summary>
/// Converts shape objects to strings, if they are not already a string
/// </summary>
public class ShapeConverter
{
private static readonly GeoJsonWktConverter GeoJsonConverter = new GeoJsonWktConverter();
Expand All @@ -18,6 +21,13 @@ public class ShapeConverter

public virtual bool TryConvert(object value, out string result)
{
var s = value as string;
if (s != null)
{
result = s;
return true;
}

var jValue = value as RavenJValue;
if (jValue != null && jValue.Type == JTokenType.String)
{
Expand Down
103 changes: 0 additions & 103 deletions Raven.Database/Indexing/Spatial/RavenShapeConverter.cs

This file was deleted.

93 changes: 93 additions & 0 deletions Raven.Database/Indexing/Spatial/ShapeStringConverter.cs
@@ -0,0 +1,93 @@
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Raven.Abstractions.Indexing;

namespace Raven.Database.Indexing.Spatial
{
/// <summary>
/// Converts spatial strings to WKT, if they aren't already
/// </summary>
public class ShapeStringConverter
{
private readonly SpatialOptions options;

private static readonly Regex RegexGeoUriCoord = new Regex(@"^ ([+-]?(?:\d+\.?\d*|\d*\.?\d+)) \s* , \s* ([+-]?(?:\d+\.?\d*|\d*\.?\d+)) \s* ,? \s* ([+-]?(?:\d+\.?\d*|\d*\.?\d+))? $",
RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex RegexGeoUriUncert = new Regex(@"^ u \s* = \s* ([+-]?(?:\d+\.?\d*|\d*\.?\d+)) $",
RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Compiled);

public ShapeStringConverter(SpatialOptions options)
{
this.options = options;
}

public string ConvertToWKT(string shape)
{
if (!string.IsNullOrWhiteSpace(shape))
{
shape = shape.Trim();
string result;
if (TryParseGeoUri(shape, out result))
return result;

return shape;
}

return default(string);
}

private bool TryParseGeoUri(string uriString, out string shape)
{
shape = default(string);

if (!uriString.StartsWith("geo:"))
return false;

var components = uriString.Substring(4, uriString.Length - 4).Split(';').Select(x => x.Trim());

double[] coordinate = null;
var uncertainty = double.NaN;

foreach (var component in components)
{
var coord = RegexGeoUriCoord.Match(component);
if (coord.Success)
{
if (coord.Groups.Count > 1)
coordinate = new[] { double.Parse(coord.Groups[1].Value), double.Parse(coord.Groups[2].Value) };
continue;
}
var u = RegexGeoUriUncert.Match(component);
if (u.Success)
{
uncertainty = double.Parse(u.Groups[1].Value);

// Uncertainty is in meters when in a geographic context
if (uncertainty > 0 && options.Type != SpatialFieldType.Geography)
uncertainty = uncertainty / 1000;
}
}

if (coordinate == null)
return false;

if (!double.IsNaN(uncertainty) && uncertainty > 0)
shape = MakeCircle(coordinate[0], coordinate[1], uncertainty);
else
shape = MakePoint(coordinate[0], coordinate[1]);

return true;
}

protected string MakePoint(double x, double y)
{
return string.Format(CultureInfo.InvariantCulture, "POINT ({0} {1})", x, y);
}

protected string MakeCircle(double x, double y, double radius)
{
return string.Format(CultureInfo.InvariantCulture, "Circle({0} {1} d={2})", x, y, radius);
}
}
}
88 changes: 88 additions & 0 deletions Raven.Database/Indexing/Spatial/ShapeStringReadWriter.cs
@@ -0,0 +1,88 @@
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using Raven.Abstractions.Indexing;
using Raven.Abstractions.Spatial;
using Spatial4n.Core.Context.Nts;
using Spatial4n.Core.Io;
using Spatial4n.Core.Shapes;

namespace Raven.Database.Indexing.Spatial
{
/// <summary>
/// Reads and writes shape strings
/// </summary>
public class ShapeStringReadWriter
{
private static readonly WktSanitizer WktSanitizer = new WktSanitizer();
private static NtsShapeReadWriter geoShapeReadWriter;

private readonly SpatialOptions options;
private readonly NtsShapeReadWriter ntsShapeReadWriter;
private readonly ShapeStringConverter shapeStringConverter;

public ShapeStringReadWriter(SpatialOptions options, NtsSpatialContext context)
{
this.options = options;
this.ntsShapeReadWriter = CreateNtsShapeReadWriter(options, context);
this.shapeStringConverter = new ShapeStringConverter(options);
}

private NtsShapeReadWriter CreateNtsShapeReadWriter(SpatialOptions opt, NtsSpatialContext ntsContext)
{
if (opt.Type == SpatialFieldType.Cartesian)
return new NtsShapeReadWriter(ntsContext);
return geoShapeReadWriter ?? (geoShapeReadWriter = new NtsShapeReadWriter(ntsContext));
}

public Shape ReadShape(string shape)
{
shape = shapeStringConverter.ConvertToWKT(shape);
shape = WktSanitizer.Sanitize(shape);

// Circle translation should be done last, before passing to NtsShapeReadWriter
if (options.Type == SpatialFieldType.Geography)
shape = TranslateCircleFromKmToRadians(shape);

return ntsShapeReadWriter.ReadShape(shape);
}

public string WriteShape(Shape shape)
{
return ntsShapeReadWriter.WriteShape(shape);
}

private double TranslateCircleFromKmToRadians(double radius)
{
return (radius / EarthMeanRadiusKm) * RadiansToDegrees;
}

private string TranslateCircleFromKmToRadians(string shapeWKT)
{
var match = CircleShape.Match(shapeWKT);
if (match.Success == false)
return shapeWKT;

var radCapture = match.Groups[3];
var radius = double.Parse(radCapture.Value, CultureInfo.InvariantCulture);

radius = TranslateCircleFromKmToRadians(radius);

return shapeWKT.Substring(0, radCapture.Index) + radius.ToString("F6", CultureInfo.InvariantCulture) +
shapeWKT.Substring(radCapture.Index + radCapture.Length);
}

/// <summary>
/// The International Union of Geodesy and Geophysics says the Earth's mean radius in KM is:
///
/// [1] http://en.wikipedia.org/wiki/Earth_radius
/// </summary>
private const double EarthMeanRadiusKm = 6371.0087714;
private const double DegreesToRadians = Math.PI / 180;
private const double RadiansToDegrees = 1 / DegreesToRadians;

private static readonly Regex CircleShape =
new Regex(@"Circle \s* \( \s* ([+-]?(?:\d+\.?\d*|\d*\.?\d+)) \s+ ([+-]?(?:\d+\.?\d*|\d*\.?\d+)) \s+ d=([+-]?(?:\d+\.?\d*|\d*\.?\d+)) \s* \)",
RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
}

0 comments on commit 92792ca

Please sign in to comment.