Skip to content

Commit 224ae40

Browse files
committed
[Branches/1.0]
- Initial commit of GeoPackage provider (work in progres) git-tfs-id: [https://tfs.codeplex.com/tfs/TFS01]$/SharpMap/Branches/1.0;C107174
1 parent 4aadb33 commit 224ae40

17 files changed

Lines changed: 2213 additions & 0 deletions
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Data.SQLite;
5+
using SharpMap.Data.Providers.Tiles;
6+
using SharpMap.Layers;
7+
using SharpMap.Styles;
8+
9+
namespace SharpMap.Data.Providers
10+
{
11+
/// <summary>
12+
/// A class wrapping a GeoPackage
13+
/// </summary>
14+
/// <seealso href="http://www.geopackage.org/"/>
15+
public class GeoPackage
16+
{
17+
/// <summary>
18+
/// Method to open a geopackage file
19+
/// </summary>
20+
/// <param name="filename">The filename of the GeoPackage</param>
21+
/// <param name="password">The password to access the GeoPackage</param>
22+
/// <returns></returns>
23+
public static GeoPackage Open(string filename, string password = null)
24+
{
25+
try
26+
{
27+
GpkgUtility.CheckRequirements(filename, password);
28+
return new GeoPackage(GpkgUtility.CreateConnectionString(filename, password));
29+
}
30+
catch (Exception)
31+
{
32+
throw new GeoPackageException(string.Format("Failed to open GeoPackage '{0}'", filename));
33+
}
34+
}
35+
36+
private readonly string _connectionString;
37+
38+
/// <summary>
39+
/// Creates an instance of this class
40+
/// </summary>
41+
/// <param name="connectionString"></param>
42+
private GeoPackage(string connectionString)
43+
{
44+
_connectionString = connectionString;
45+
}
46+
47+
/// <summary>
48+
/// Gets the <see cref="GpkgContent">Features</see> stored in the GeoPackage
49+
/// </summary>
50+
public ReadOnlyCollection<GpkgContent> Features
51+
{
52+
get
53+
{
54+
return new ReadOnlyCollection<GpkgContent>(ReadContents("features"));
55+
}
56+
}
57+
58+
/// <summary>
59+
/// Gets the <see cref="GpkgContent">Tiles</see> stored in the GeoPackage
60+
/// </summary>
61+
public ReadOnlyCollection<GpkgContent> Tiles
62+
{
63+
get
64+
{
65+
return new ReadOnlyCollection<GpkgContent>(ReadContents("tiles"));
66+
}
67+
}
68+
69+
/// <summary>
70+
/// Method to read the content from the 'gpkg_contents' table, filtered by <see cref="kind"/>
71+
/// </summary>
72+
/// <param name="kind">The kind of content to filter for</param>
73+
/// <returns>A list of content</returns>
74+
private IList<GpkgContent> ReadContents(string kind)
75+
{
76+
var res = new List<GpkgContent>();
77+
using (var cn = new SQLiteConnection(_connectionString))
78+
{
79+
cn.Open();
80+
var cmd = new SQLiteCommand("SELECT * FROM \"gpkg_contents\" WHERE \"data_type\"='" + kind + "';");
81+
var rdr = cmd.ExecuteReader();
82+
while (rdr.Read())
83+
res.Add(new GpkgContent(rdr));
84+
}
85+
return res;
86+
}
87+
88+
/// <summary>
89+
/// Method to create an async tile layer
90+
/// </summary>
91+
/// <param name="name">The name of the layer</param>
92+
/// <returns>A tile layer</returns>
93+
public ILayer GetTileLayer(string name)
94+
{
95+
var content = FindContent(Tiles, name);
96+
if (content == null)
97+
throw new ArgumentException(string.Format("No tile layer named '{0}'", name));
98+
99+
var schema = new GpkgTileSchema(content);
100+
return new TileLayer(new GpkgTileSource(content, schema), content.TableName);
101+
}
102+
103+
/// <summary>
104+
/// Method to create an async tile layer
105+
/// </summary>
106+
/// <param name="name">The name of the layer</param>
107+
/// <returns>A tile layer</returns>
108+
public ITileAsyncLayer GetTileAsyncLayer(string name)
109+
{
110+
var content = FindContent(Tiles, name);
111+
if (content == null)
112+
throw new ArgumentException(string.Format("No tile layer named '{0}'", name));
113+
114+
var schema = new GpkgTileSchema(content);
115+
return new TileAsyncLayer(new GpkgTileSource(content, schema), content.TableName);
116+
}
117+
118+
public IProvider GetFeatureProvider(string name)
119+
{
120+
var content = FindContent(Features, name);
121+
if (content == null)
122+
throw new ArgumentException(string.Format("No feature layer named '{0}'", name));
123+
124+
var p = new GpkgProvider(content);
125+
return p;
126+
}
127+
128+
public ILayer GetFeatureLayer(GpkgContent content, Func<GpkgContent, IProvider, ILayer> createLayer = null)
129+
{
130+
createLayer = createLayer ?? CreateVectorLayer;
131+
return createLayer(content, GetFeatureProvider(content.TableName));
132+
}
133+
134+
#region private helper methods
135+
private static ILayer CreateVectorLayer(GpkgContent content, IProvider provider)
136+
{
137+
return new VectorLayer(content.TableName, provider) { Style = VectorStyle.CreateRandomStyle() };
138+
}
139+
140+
private static GpkgContent FindContent(IEnumerable<GpkgContent> contents, string contentName)
141+
{
142+
foreach (var content in contents)
143+
{
144+
if (string.Equals(content.TableName, contentName, StringComparison.CurrentCultureIgnoreCase))
145+
return content;
146+
}
147+
return null;
148+
}
149+
#endregion
150+
151+
}
152+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using System.Runtime.Serialization;
3+
4+
namespace SharpMap.Data.Providers
5+
{
6+
[Serializable]
7+
public class GeoPackageException : Exception
8+
{
9+
//
10+
// For guidelines regarding the creation of new exception types, see
11+
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
12+
// and
13+
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
14+
//
15+
16+
public GeoPackageException()
17+
{
18+
}
19+
20+
public GeoPackageException(string message) : base(message)
21+
{
22+
}
23+
24+
public GeoPackageException(string message, Exception inner) : base(message, inner)
25+
{
26+
}
27+
28+
protected GeoPackageException(
29+
SerializationInfo info,
30+
StreamingContext context) : base(info, context)
31+
{
32+
}
33+
}
34+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
using System;
2+
using System.IO;
3+
using GeoAPI.DataStructures;
4+
using GeoAPI.Geometries;
5+
6+
namespace SharpMap.Data.Providers.Geometry
7+
{
8+
internal class GpkgBinaryHeader
9+
{
10+
private const byte IsEmptyFlag = 0x01 << 5;
11+
private const byte EndianessFlag = 0x01;
12+
private const byte ExtentFlags = 0x07 << 1;
13+
14+
internal enum GeoPackageBinaryType : byte
15+
{
16+
Standard, Extended
17+
}
18+
19+
private byte[] _magic = new byte[]{ 0x47, 0x50 };
20+
private byte _version;
21+
private byte _flags;
22+
private int _srs_id;
23+
private Envelope _extent;
24+
private Interval _zrange;
25+
private Interval _mrange;
26+
27+
28+
public static GpkgBinaryHeader Read(BinaryReader reader)
29+
{
30+
var res = new GpkgBinaryHeader();
31+
res._magic = reader.ReadBytes(2);
32+
res._version = reader.ReadByte();
33+
res._flags = reader.ReadByte();
34+
35+
switch (res.Endianess)
36+
{
37+
case 0:
38+
ReadSridExtent(reader, res);
39+
break;
40+
case 1:
41+
ReadBESridExtent(reader, res);
42+
break;
43+
}
44+
45+
return res;
46+
}
47+
48+
private static void ReadSridExtent(BinaryReader reader, GpkgBinaryHeader header)
49+
{
50+
header._srs_id = reader.ReadInt32();
51+
var ordinates = header.Ordinates;
52+
if (ordinates == Ordinates.None)
53+
{
54+
header._extent = new Envelope();
55+
header._zrange = Interval.Create();
56+
header._mrange = Interval.Create();
57+
return;
58+
}
59+
header._extent = new Envelope(reader.ReadDouble(), reader.ReadDouble(),
60+
reader.ReadDouble(), reader.ReadDouble());
61+
if ((ordinates & Ordinates.Z) == Ordinates.Z)
62+
header._zrange = Interval.Create(reader.ReadDouble(), reader.ReadDouble());
63+
64+
if ((ordinates & Ordinates.M) == Ordinates.M)
65+
header._mrange = Interval.Create(reader.ReadDouble(), reader.ReadDouble());
66+
}
67+
68+
private static void ReadBESridExtent(BinaryReader reader, GpkgBinaryHeader header)
69+
{
70+
header._srs_id = SwapByteOrder(reader.ReadInt32());
71+
var ordinates = header.Ordinates;
72+
if (ordinates == Ordinates.None)
73+
{
74+
header._extent = new Envelope();
75+
header._zrange = Interval.Create();
76+
header._mrange = Interval.Create();
77+
return;
78+
}
79+
header._extent = new Envelope(SwapByteOrder(reader.ReadDouble()), SwapByteOrder(reader.ReadDouble()),
80+
SwapByteOrder(reader.ReadDouble()), SwapByteOrder(reader.ReadDouble()));
81+
if ((ordinates & Ordinates.Z) == Ordinates.Z)
82+
header._zrange = Interval.Create(SwapByteOrder(reader.ReadDouble()), SwapByteOrder(reader.ReadDouble()));
83+
84+
if ((ordinates & Ordinates.M) == Ordinates.M)
85+
header._mrange = Interval.Create(SwapByteOrder(reader.ReadDouble()), SwapByteOrder(reader.ReadDouble()));
86+
}
87+
88+
private static int SwapByteOrder(int val)
89+
{
90+
var bytes = BitConverter.GetBytes(val);
91+
Array.Reverse(bytes);
92+
return BitConverter.ToInt32(bytes, 0);
93+
}
94+
95+
/// <summary>
96+
/// Method to swap the byte order
97+
/// </summary>
98+
/// <param name="val"></param>
99+
/// <returns></returns>
100+
private static double SwapByteOrder(double val)
101+
{
102+
var bytes = BitConverter.GetBytes(val);
103+
Array.Reverse(bytes);
104+
return BitConverter.ToDouble(bytes, 0);
105+
}
106+
107+
/// <summary>
108+
/// Gets a value indicating the ordinates
109+
/// </summary>
110+
public Ordinates Ordinates
111+
{
112+
get
113+
{
114+
switch ((_flags & ExtentFlags) >> 1)
115+
{
116+
case 0:
117+
return Ordinates.None;
118+
case 1:
119+
return Ordinates.XY;
120+
case 2:
121+
return Ordinates.XYZ;
122+
case 3:
123+
return Ordinates.XYM;
124+
case 4:
125+
return Ordinates.XYZM;
126+
}
127+
throw new GeoPackageException("Invalid extent flags");
128+
}
129+
}
130+
131+
internal int NumOrdinates
132+
{
133+
get
134+
{
135+
switch ((_flags & ExtentFlags) >> 1)
136+
{
137+
case 0:
138+
return 0;
139+
case 1:
140+
return 2;
141+
case 2:
142+
case 3:
143+
return 3;
144+
case 4:
145+
return 4;
146+
}
147+
throw new GeoPackageException();
148+
}
149+
}
150+
151+
/// <summary>
152+
/// Gets a value indicating that this geometry is empty
153+
/// </summary>
154+
public bool IsEmpty { get { return (_flags & IsEmptyFlag) == 0; }}
155+
156+
/// <summary>
157+
/// Gets a value indicating that this geometry is empty
158+
/// </summary>
159+
public int Endianess { get { return (_flags & EndianessFlag); } }
160+
161+
/// <summary>
162+
/// Gets the magic number
163+
/// </summary>
164+
public byte[] Magic { get { return _magic; }}
165+
166+
/// <summary>
167+
/// Gets a value indicating the version of the geometry data
168+
/// </summary>
169+
public byte Version { get { return _version; }}
170+
171+
/// <summary>
172+
/// Gets a value indicating the spatial reference id
173+
/// </summary>
174+
public int SrsId { get { return _srs_id; } }
175+
176+
public Envelope Extent
177+
{
178+
get { return _extent; }
179+
}
180+
181+
public Interval ZRange { get { return _zrange; }}
182+
public Interval MRange { get { return _mrange; } }
183+
}
184+
}

0 commit comments

Comments
 (0)