Line data Source code
1 : import 'dart:math';
2 :
3 : import 'package:csv/csv.dart';
4 : import 'package:geocoder_offline/geocoder_offline.dart';
5 : import 'package:geocoder_offline/src/SearchData.dart';
6 : import 'package:kdtree/kdtree.dart';
7 :
8 : import 'LocationData.dart';
9 :
10 : class GeocodeData {
11 : /// List of possible bearings
12 : final List<String> bearings = <String>[
13 : 'N',
14 : 'NNE',
15 : 'NE',
16 : 'ENE',
17 : 'E',
18 : 'ESE',
19 : 'SE',
20 : 'SSE',
21 : 'S',
22 : 'SSW',
23 : 'SW',
24 : 'WSW',
25 : 'W',
26 : 'WNW',
27 : 'NW',
28 : 'NNW',
29 : 'N'
30 : ];
31 :
32 : /// Angle that every possible bearings covers
33 : final double DIRECTION_RANGE = 22.5;
34 :
35 : /// String that contains all possible Location
36 : final String inputString;
37 :
38 : /// Field Delimiter in inputString
39 : final String fieldDelimiter;
40 :
41 : /// Text Delimiter in inputString
42 : final String textDelimiter;
43 :
44 : /// End of line character in inputString
45 : final String eol;
46 :
47 : /// Name of column that contains Location name
48 : final String featureNameHeader;
49 :
50 : /// Name of column that contains Location state
51 : final String stateHeader;
52 :
53 : /// Name of column that contains Location latitude
54 : final String latitudeHeader;
55 :
56 : /// Name of column that contains Location longitude
57 : final String longitudeHeader;
58 :
59 : /// Number of nearest result
60 : final int numMarkers;
61 :
62 : KDTree _kdTree;
63 : int _featureNameHeaderSN;
64 : int _stateHeaderSN;
65 : int _latitudeHeaderSN;
66 : int _longitudeHeaderSN;
67 :
68 1 : GeocodeData(this.inputString, this.featureNameHeader, this.stateHeader,
69 : this.latitudeHeader, this.longitudeHeader,
70 : {this.numMarkers = 1,
71 : this.fieldDelimiter = defaultFieldDelimiter,
72 : this.textDelimiter = defaultTextDelimiter,
73 : this.eol = defaultEol}) {
74 2 : var rowsAsListOfValues = const CsvToListConverter().convert(inputString,
75 1 : fieldDelimiter: fieldDelimiter,
76 1 : textDelimiter: textDelimiter,
77 1 : eol: eol,
78 : shouldParseNumbers: false);
79 :
80 1 : _featureNameHeaderSN =
81 5 : rowsAsListOfValues[0].indexWhere((x) => x == featureNameHeader);
82 6 : _stateHeaderSN = rowsAsListOfValues[0].indexWhere((x) => x == stateHeader);
83 1 : _latitudeHeaderSN =
84 5 : rowsAsListOfValues[0].indexWhere((x) => x == latitudeHeader);
85 1 : _longitudeHeaderSN =
86 5 : rowsAsListOfValues[0].indexWhere((x) => x == longitudeHeader);
87 :
88 3 : if (_featureNameHeaderSN == -1 ||
89 3 : _stateHeaderSN == -1 ||
90 3 : _latitudeHeaderSN == -1 ||
91 3 : _longitudeHeaderSN == -1) {
92 0 : throw Exception('Some of header is not find in file');
93 : }
94 :
95 : var locations = rowsAsListOfValues
96 1 : .sublist(1)
97 3 : .map((model) => LocationData(
98 2 : model[_featureNameHeaderSN],
99 2 : model[_stateHeaderSN],
100 4 : double.tryParse(model[_latitudeHeaderSN].toString()) ?? -1,
101 4 : double.tryParse(model[_longitudeHeaderSN].toString()) ?? -1))
102 3 : .map((model) => model.toJson())
103 1 : .toList();
104 :
105 4 : _kdTree = KDTree(locations, _distance, ['latitude', 'longitude']);
106 : }
107 :
108 1 : double _distance(location1, location2) {
109 1 : var lat1 = location1['latitude'],
110 1 : lon1 = location1['longitude'],
111 1 : lat2 = location2['latitude'],
112 1 : lon2 = location2['longitude'];
113 :
114 1 : return calculateDistance(lat1, lon1, lat2, lon2);
115 : }
116 :
117 1 : static double _deg2rad(deg) {
118 2 : return deg * (pi / 180);
119 : }
120 :
121 1 : static double _rad2deg(rad) {
122 2 : return rad * (180 / pi);
123 : }
124 :
125 1 : String _calculateBearing(double lat1, double lon1, double lat2, double lon2) {
126 : if (lat1 == null || lon1 == null || lat2 == null || lon2 == null) {
127 : return null;
128 : }
129 :
130 1 : var latitude1 = _deg2rad(lat1);
131 1 : var latitude2 = _deg2rad(lat2);
132 2 : var longDiff = _deg2rad(lon2 - lon1);
133 3 : var y = sin(longDiff) * cos(latitude2);
134 4 : var x = cos(latitude1) * sin(latitude2) -
135 5 : sin(latitude1) * cos(latitude2) * cos(longDiff);
136 :
137 5 : var degrees = ((_rad2deg(atan2(y, x)) + 360) % 360) - 11.25;
138 :
139 2 : var index = (degrees ~/ DIRECTION_RANGE);
140 :
141 2 : return bearings[index];
142 : }
143 :
144 1 : List<LocationResult> search(double latitute, double longitude) {
145 1 : var result = <LocationResult>[];
146 1 : var point = {'latitude': latitute, 'longitude': longitude};
147 3 : var nearest = _kdTree.nearest(point, numMarkers);
148 1 : var searchData = SearchData(latitute, longitude);
149 :
150 2 : nearest.forEach((x) {
151 2 : var location = LocationData.fromJson(x[0]);
152 1 : double distance = x[1];
153 1 : var bearing = _calculateBearing(
154 2 : location.latitude, location.longitude, latitute, longitude);
155 2 : result.add(LocationResult(location, distance, bearing, searchData));
156 : });
157 :
158 : return result;
159 : }
160 :
161 1 : static double calculateDistance(
162 : double latStart, double lonStart, double latEnd, double lonEnd) {
163 : var R = 3958.8; // Radius of the earth in miles
164 2 : var dLat = _deg2rad(latEnd - latStart); // deg2rad below
165 2 : var dLon = _deg2rad(lonEnd - lonStart);
166 6 : var a = sin(dLat / 2) * sin(dLat / 2) +
167 3 : cos(_deg2rad(latStart)) *
168 3 : cos(_deg2rad(latEnd)) *
169 3 : sin(dLon / 2) *
170 2 : sin(dLon / 2);
171 5 : var c = 2 * atan2(sqrt(a), sqrt(1 - a));
172 1 : var d = R * c; // Distance in miles
173 : return d;
174 : }
175 : }
|