Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Geo URI strings can now be used in queries
- Loading branch information
1 parent
26a030e
commit 92792ca
Showing
9 changed files
with
266 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Oops, something went wrong.