diff --git a/.gitignore b/.gitignore index b167e9d..5d5845c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Dependencies +Components/ + # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) [Bb]in/ [Oo]bj/ diff --git a/SQLiteExtensions/SQLiteExtensions.sln b/SQLiteExtensions/SQLiteExtensions.sln new file mode 100644 index 0000000..3246285 --- /dev/null +++ b/SQLiteExtensions/SQLiteExtensions.sln @@ -0,0 +1,32 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLiteExtensions_iOS", "iOS\SQLiteExtensions_iOS.csproj", "{50EEF6C9-7587-4094-8161-0F926975A307}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|iPhoneSimulator = Debug|iPhoneSimulator + Release|iPhoneSimulator = Release|iPhoneSimulator + Debug|iPhone = Debug|iPhone + Release|iPhone = Release|iPhone + Ad-Hoc|iPhone = Ad-Hoc|iPhone + AppStore|iPhone = AppStore|iPhone + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {50EEF6C9-7587-4094-8161-0F926975A307}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {50EEF6C9-7587-4094-8161-0F926975A307}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {50EEF6C9-7587-4094-8161-0F926975A307}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {50EEF6C9-7587-4094-8161-0F926975A307}.AppStore|iPhone.Build.0 = AppStore|iPhone + {50EEF6C9-7587-4094-8161-0F926975A307}.Debug|iPhone.ActiveCfg = Debug|iPhone + {50EEF6C9-7587-4094-8161-0F926975A307}.Debug|iPhone.Build.0 = Debug|iPhone + {50EEF6C9-7587-4094-8161-0F926975A307}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {50EEF6C9-7587-4094-8161-0F926975A307}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {50EEF6C9-7587-4094-8161-0F926975A307}.Release|iPhone.ActiveCfg = Release|iPhone + {50EEF6C9-7587-4094-8161-0F926975A307}.Release|iPhone.Build.0 = Release|iPhone + {50EEF6C9-7587-4094-8161-0F926975A307}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {50EEF6C9-7587-4094-8161-0F926975A307}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = iOS\SQLiteExtensions_iOS.csproj + EndGlobalSection +EndGlobal diff --git a/SQLiteExtensions/Shared/Place.cs b/SQLiteExtensions/Shared/Place.cs new file mode 100644 index 0000000..535c725 --- /dev/null +++ b/SQLiteExtensions/Shared/Place.cs @@ -0,0 +1,32 @@ +using System; + +namespace Touchin.SQLiteExtensions +{ + public class Place + { + public int Id { get; set; } + + public double Latitude { get; set; } + + public double Longitude { get; set; } + + public double Distance { get; set; } + + public string GetHumanReadableCoordinate() + { + return + GetHumanReadableDegrees (Math.Abs (Latitude)) + (Latitude < 0 ? "S" : "N") + + ", " + + GetHumanReadableDegrees (Math.Abs (Longitude)) + (Longitude < 0 ? "W" : "E"); + } + + private string GetHumanReadableDegrees (double value) + { + var degrees = Math.Truncate (value); + var minutes = Math.Truncate ((value - degrees) * 60); + var seconds = (((value - degrees) * 60) - minutes) * 60; + return String.Format ("{0:00}\u00B0{1:00}\u2032{2:00}\u2033", degrees, minutes, seconds); + } + } +} + diff --git a/SQLiteExtensions/Shared/PlaceService.cs b/SQLiteExtensions/Shared/PlaceService.cs new file mode 100644 index 0000000..99eb0c7 --- /dev/null +++ b/SQLiteExtensions/Shared/PlaceService.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using SQLite; + +namespace Touchin.SQLiteExtensions +{ + public class PlaceService : IDisposable + { + private readonly SQLiteConnection _databaseConnection; + + public PlaceService (string databasePath) + { + _databaseConnection = new SQLiteConnection (databasePath); + _databaseConnection.CreateDistanceFunction (); + } + + public Place[] GetPlaces(double latitude, double longitude) + { + const string query = @" +SELECT *, DISTANCE(Latitude, Longitude, @latitude, @longitude) AS Distance + FROM Place + WHERE Distance < @radius + ORDER BY Distance +"; + var command = _databaseConnection.CreateCommand (query); + command.Bind ("@latitude", latitude); + command.Bind ("@longitude", longitude); + command.Bind ("@radius", 100d); + return command.ExecuteDeferredQuery ().ToArray (); + } + + public int GetPlaceCount() + { + const string query = @" +SELECT COUNT(*) FROM Place +"; + return _databaseConnection.ExecuteScalar (query); + } + + public void Dispose () + { + _databaseConnection.Dispose (); + } + } +} + diff --git a/SQLiteExtensions/Shared/SQLite3Extensions.Distance.cs b/SQLiteExtensions/Shared/SQLite3Extensions.Distance.cs new file mode 100644 index 0000000..796830c --- /dev/null +++ b/SQLiteExtensions/Shared/SQLite3Extensions.Distance.cs @@ -0,0 +1,52 @@ +using System; +using System.Runtime.InteropServices; +using SQLite; + +namespace Touchin.SQLiteExtensions +{ + public static partial class SQLite3Extensions + { + public static void CreateDistanceFunction (this SQLiteConnection connection) + { + connection.CreateFunction ("DISTANCE", 4, DistanceBody); + } + + private static double Distance (double latitude1, double longitude1, double latitude2, double longitude2) + { + const double earthRadius = 6378.1; + + double lat1 = latitude1 * Math.PI / 180; + double lon1 = longitude1 * Math.PI / 180; + double lat2 = latitude2 * Math.PI / 180; + double lon2 = longitude2 * Math.PI / 180; + + return earthRadius * Math.Acos (Math.Sin (lat1) * Math.Sin (lat2) + Math.Cos (lat1) * Math.Cos (lat2) * Math.Cos (lon2 - lon1)); + } + +#if __IOS__ + // https://bugzilla.novell.com/show_bug.cgi?id=576775 + [MonoTouch.MonoPInvokeCallback (typeof (Action))] +#endif + private static void DistanceBody (IntPtr ctx, int argc, IntPtr argv) + { + IntPtr[] args = ExtractArgs (argc, argv); + + if (ValueType (args [0]) != SQLite3.ColType.Float || + ValueType (args [1]) != SQLite3.ColType.Float || + ValueType (args [2]) != SQLite3.ColType.Float || + ValueType (args [3]) != SQLite3.ColType.Float) + { + ResultNull (ctx); + } + else + { + double latitude1 = ValueDouble (args [0]); + double longitude1 = ValueDouble (args [1]); + double latitude2 = ValueDouble (args [2]); + double longitude2 = ValueDouble (args [3]); + double result = Distance (latitude1, longitude1, latitude2, longitude2); + ResultDouble (ctx, result); + } + } + } +} \ No newline at end of file diff --git a/SQLiteExtensions/Shared/SQLite3Extensions.cs b/SQLiteExtensions/Shared/SQLite3Extensions.cs new file mode 100644 index 0000000..9241489 --- /dev/null +++ b/SQLiteExtensions/Shared/SQLite3Extensions.cs @@ -0,0 +1,82 @@ +using System; +using System.Runtime.InteropServices; +using SQLite; + +namespace Touchin.SQLiteExtensions +{ + public static partial class SQLite3Extensions + { + private static void CreateFunction (this SQLiteConnection connection, string functionName, int paramCount, Action functionBody) + { + var result = CreateFunction (connection.Handle, functionName, paramCount, TextEncoding.UTF8, IntPtr.Zero, functionBody, null, null); + if (result != SQLite3.Result.OK) + { + throw SQLiteException.New (result, "Can not create function"); + } + } + + private static IntPtr [] ExtractArgs (int argc, IntPtr argv) + { + IntPtr [] args = new IntPtr [argc]; + Marshal.Copy (argv, args, 0, argc); + return args; + } + + // SQLITE_API int sqlite3_create_function( + // sqlite3 *db, + // const char *zFunctionName, + // int nArg, + // int eTextRep, + // void *pApp, + // void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + // void (*xStep)(sqlite3_context*,int,sqlite3_value**), + // void (*xFinal)(sqlite3_context*) + // ); + [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_create_function")] + private static extern SQLite3.Result CreateFunction ( + IntPtr db, + string zFunctionName, + int nArg, + TextEncoding eTextRep, + IntPtr pApp, + Action xFunc, + Action xStep, + Action xFinal + ); + + #region sqlite3_value + + // SQLITE_API int sqlite3_value_type(sqlite3_value*); + [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_value_type")] + private static extern SQLite3.ColType ValueType (IntPtr value); + + // SQLITE_API double sqlite3_value_double(sqlite3_value*); + [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_value_double")] + private static extern double ValueDouble (IntPtr value); + + #endregion + + #region sqlite3_context + + // SQLITE_API void sqlite3_result_null(sqlite3_context*); + [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_result_null")] + private static extern void ResultNull (IntPtr ctx); + + // SQLITE_API void sqlite3_result_double(sqlite3_context*, double); + [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_result_double")] + private static extern void ResultDouble (IntPtr ctx, double value); + + #endregion + + // These constant define integer codes that represent the various text encodings supported by SQLite. + private enum TextEncoding + { + UTF8 = 1, + UTF16LE = 2, + UTF16BE = 3, + UTF16 = 4, /* Use native byte order */ + Any = 5, /* sqlite3_create_function only */ + UTF16Aligned = 8, /* sqlite3_create_collation only */ + } + } +} \ No newline at end of file diff --git a/SQLiteExtensions/iOS/AppDelegate.cs b/SQLiteExtensions/iOS/AppDelegate.cs new file mode 100644 index 0000000..d599bfb --- /dev/null +++ b/SQLiteExtensions/iOS/AppDelegate.cs @@ -0,0 +1,31 @@ +using MonoTouch.Foundation; +using MonoTouch.UIKit; + +namespace Touchin.SQLiteExtensions +{ + [Register (Name)] + public partial class AppDelegate : UIApplicationDelegate + { + private const string Name = "AppDelegate"; + + UIWindow _window; + UIViewController _viewController; + + public override bool FinishedLaunching (UIApplication app, NSDictionary options) + { + _window = new UIWindow (UIScreen.MainScreen.Bounds); + + _viewController = new MainViewController (); + _window.RootViewController = new UINavigationController (_viewController); + _window.MakeKeyAndVisible (); + + return true; + } + + private static void Main (string[] args) + { + UIApplication.Main (args, null, Name); + } + } +} + diff --git a/SQLiteExtensions/iOS/Entitlements.plist b/SQLiteExtensions/iOS/Entitlements.plist new file mode 100644 index 0000000..9ae5993 --- /dev/null +++ b/SQLiteExtensions/iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/SQLiteExtensions/iOS/Info.plist b/SQLiteExtensions/iOS/Info.plist new file mode 100644 index 0000000..f770b0c --- /dev/null +++ b/SQLiteExtensions/iOS/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDisplayName + SQLiteExtensions + CFBundleIdentifier + com.touchin.sqlite-extensions + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 6.0 + UIDeviceFamily + + 1 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + XSLaunchImageAssets + Resources/Images.xcassets/LaunchImage.launchimage + + diff --git a/SQLiteExtensions/iOS/MainViewController.cs b/SQLiteExtensions/iOS/MainViewController.cs new file mode 100644 index 0000000..bc44296 --- /dev/null +++ b/SQLiteExtensions/iOS/MainViewController.cs @@ -0,0 +1,83 @@ +using System; +using System.Diagnostics; +using System.Linq; +using MonoTouch.CoreLocation; +using MonoTouch.Foundation; +using MonoTouch.UIKit; +using SQLite; + +namespace Touchin.SQLiteExtensions +{ + public class MainViewController : UITableViewController + { + private readonly PlaceService _placeService; + private readonly CLLocationManager _locationManager; + private readonly Stopwatch _stopwatch; + + private int _totalCount; + private Place[] _places; + + public MainViewController () + { + Title = "SQLite Extensions"; + + var databasePath = NSBundle.MainBundle.PathForResource ("Place", "sqlite"); + _placeService = new PlaceService (databasePath); + _locationManager = new CLLocationManager(); + _stopwatch = new Stopwatch (); + } + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + + TableView.AllowsSelection = false; + } + + public override void ViewWillAppear (bool animated) + { + base.ViewWillAppear (animated); + + _totalCount = _placeService.GetPlaceCount (); + + if (CLLocationManager.LocationServicesEnabled) + { + _locationManager.LocationsUpdated += HandleLocationsUpdated; + _locationManager.StartUpdatingLocation (); + } + } + + private void HandleLocationsUpdated (object sender, CLLocationsUpdatedEventArgs e) + { + var location = e.Locations.Last (); + + _stopwatch.Restart (); + _places = _placeService.GetPlaces (location.Coordinate.Latitude, location.Coordinate.Longitude); + _stopwatch.Stop (); + + TableView.ReloadData (); + } + + public override int RowsInSection (UITableView tableview, int section) + { + return _places != null ? _places.Length : 0; + } + + public override string TitleForHeader (UITableView tableView, int section) + { + return String.Format ("Places ({0}/{1}) : {2} ms", RowsInSection (tableView, section), _totalCount, _stopwatch.ElapsedMilliseconds); + } + + public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) + { + var reuseIdentifier = "Touchin.SQLiteExtensions.MainViewController.PlaceCell"; + var cell = tableView.DequeueReusableCell (reuseIdentifier) ?? new UITableViewCell (UITableViewCellStyle.Value1, reuseIdentifier); + + var place = _places [indexPath.Row]; + cell.TextLabel.Text = place.GetHumanReadableCoordinate (); + cell.DetailTextLabel.Text = String.Format ("{0:F3} km", place.Distance); + return cell; + } + } +} + diff --git a/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Contents.json b/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000..8d5b3b4 --- /dev/null +++ b/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,140 @@ +{ + "images": [ + { + "minimum-system-version": "7.0", + "orientation": "portrait", + "extent": "full-screen", + "filename": "Default@2x.png", + "size": "640x960", + "scale": "2x", + "idiom": "iphone" + }, + { + "minimum-system-version": "7.0", + "orientation": "portrait", + "extent": "full-screen", + "filename": "Default-568h@2x.png", + "size": "640x1136", + "subtype": "retina4", + "scale": "2x", + "idiom": "iphone" + }, + { + "minimum-system-version": "7.0", + "orientation": "portrait", + "extent": "full-screen", + "size": "768x1024", + "scale": "1x", + "idiom": "ipad" + }, + { + "minimum-system-version": "7.0", + "orientation": "portrait", + "extent": "full-screen", + "size": "1536x2048", + "scale": "2x", + "idiom": "ipad" + }, + { + "minimum-system-version": "7.0", + "orientation": "landscape", + "extent": "full-screen", + "size": "1024x768", + "scale": "1x", + "idiom": "ipad" + }, + { + "minimum-system-version": "7.0", + "orientation": "landscape", + "extent": "full-screen", + "size": "2048x1536", + "scale": "2x", + "idiom": "ipad" + }, + { + "orientation": "portrait", + "extent": "full-screen", + "filename": "Default.png", + "size": "320x480", + "scale": "1x", + "idiom": "iphone" + }, + { + "orientation": "portrait", + "extent": "full-screen", + "filename": "Default@2x.png", + "size": "640x960", + "scale": "2x", + "idiom": "iphone" + }, + { + "orientation": "portrait", + "extent": "full-screen", + "filename": "Default-568h@2x.png", + "size": "640x1136", + "subtype": "retina4", + "scale": "2x", + "idiom": "iphone" + }, + { + "orientation": "portrait", + "extent": "full-screen", + "size": "768x1024", + "scale": "1x", + "idiom": "ipad" + }, + { + "orientation": "portrait", + "extent": "full-screen", + "size": "1536x2048", + "scale": "2x", + "idiom": "ipad" + }, + { + "orientation": "portrait", + "extent": "to-status-bar", + "size": "768x1004", + "scale": "1x", + "idiom": "ipad" + }, + { + "orientation": "portrait", + "extent": "to-status-bar", + "size": "1536x2008", + "scale": "2x", + "idiom": "ipad" + }, + { + "orientation": "landscape", + "extent": "full-screen", + "size": "1024x768", + "scale": "1x", + "idiom": "ipad" + }, + { + "orientation": "landscape", + "extent": "full-screen", + "size": "2048x1536", + "scale": "2x", + "idiom": "ipad" + }, + { + "orientation": "landscape", + "extent": "to-status-bar", + "size": "1024x748", + "scale": "1x", + "idiom": "ipad" + }, + { + "orientation": "landscape", + "extent": "to-status-bar", + "size": "2048x1496", + "scale": "2x", + "idiom": "ipad" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png new file mode 100644 index 0000000..11348e8 Binary files /dev/null and b/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ diff --git a/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Default.png b/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Default.png new file mode 100644 index 0000000..e2c0840 Binary files /dev/null and b/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Default.png differ diff --git a/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Default@2x.png b/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Default@2x.png new file mode 100644 index 0000000..4e11dac Binary files /dev/null and b/SQLiteExtensions/iOS/Resources/Images.xcassets/LaunchImage.launchimage/Default@2x.png differ diff --git a/SQLiteExtensions/iOS/SQLiteExtensions_iOS.csproj b/SQLiteExtensions/iOS/SQLiteExtensions_iOS.csproj new file mode 100644 index 0000000..489bdc0 --- /dev/null +++ b/SQLiteExtensions/iOS/SQLiteExtensions_iOS.csproj @@ -0,0 +1,143 @@ + + + + Debug + iPhoneSimulator + 10.0.0 + 2.0 + {50EEF6C9-7587-4094-8161-0F926975A307} + {6BC8ED88-2882-458C-8E55-DFD12B67127B};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + Touchin.SQLiteExtensions + Resources + + + true + full + false + bin\iPhoneSimulator\Debug + DEBUG;__MOBILE__;__IOS__; + prompt + 4 + false + None + Entitlements.plist + true + SQLiteExtensions + + + ARMv7 + + + full + true + bin\iPhoneSimulator\Release + __MOBILE__;__IOS__; + prompt + 4 + None + false + Entitlements.plist + SQLiteExtensions + + + true + full + false + bin\iPhone\Debug + DEBUG;__MOBILE__;__IOS__; + prompt + 4 + false + Entitlements.plist + true + iPhone Developer + SQLiteExtensions_iOS + + + full + true + bin\iPhone\Release + __MOBILE__;__IOS__; + prompt + 4 + Entitlements.plist + false + iPhone Developer + SQLiteExtensions_iOS + + + full + true + bin\iPhone\Ad-Hoc + __MOBILE__;__IOS__; + prompt + 4 + false + Entitlements.plist + true + Automatic:AdHoc + iPhone Distribution + SQLiteExtensions_iOS + + + full + true + bin\iPhone\AppStore + __MOBILE__;__IOS__; + prompt + 4 + false + Entitlements.plist + Automatic:AppStore + iPhone Distribution + SQLiteExtensions_iOS + + + + + + + + ..\Components\sqlite-net-1.0.1\lib\ios\SQLite.dll + + + + + + + + + + SQLite3Extensions.cs + + + SQLite3Extensions.Distance.cs + + + + Place.cs + + + PlaceService.cs + + + + + + + + + + + + 1.0.1 + False + + + + + Resources\Place.sqlite + + + \ No newline at end of file