-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProgram.cs
279 lines (254 loc) · 12 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Xml.Linq;
namespace KMLExtractPoints
{
/// <summary>
///
/// KMLExtractPoints: a simple windows .net app to extract points from a KML file.
///
/// </summary>
//todo: make this more automatable - perhaps "pipe"able - this particular version outputs files that need further processing
// that was handled in a spreadsheet and manual processing
//todo: JSON
//todo: feed into a specified API (securely of course)
class Program
{
static string ParamInput = "/KML:";
static string ParamOutput = "/OUT:";
static string ParamJSON = "/JSON:"; // future stuff - needs testing..
static string emptyString = "";
static bool consoleInOut = false; // for future mods to allow pipe and redirection friendly calling of this app for best automation options...
static void Main(string[] args)
{
string pInputFile = getParam(args, ParamInput, emptyString);
string pOutputFile = getParam(args, ParamOutput, emptyString);
string JSONOutput = getParam(args, ParamJSON, "N");
bool ExportJSON = (JSONOutput.ToUpper() == "Y");
//todo: make this pipe friendly ie: type myfile.kml > KMLPointsExtract /JSON:Y | KMLPointsGetW3W > Final.JSON
if (pInputFile == emptyString || pOutputFile == emptyString) { HelpUsage("Requires Parameters"); }
Console.Write($"\nKML Input: {pInputFile}"); if (!File.Exists(pInputFile)) { HelpUsage($"The Input file [{pInputFile}] does not exist.\n"); }
Console.Write($"\nKML Output: {pOutputFile}");
if (File.Exists(pOutputFile))
{
Console.WriteLine($"The Output file [{pOutputFile}] already exists - overwrite? Y/N");
ConsoleKeyInfo UserResponse = Console.ReadKey();
if (UserResponse.Key.ToString().ToUpper() == "Y")
{
File.Delete(pOutputFile);
}
else
{
HelpUsage($"The Output file [{pOutputFile}] exists and you have chosen to not overwrite it.\n");
}
}
// set up xml to read KML (learned from here: https://stackoverflow.com/questions/8822402/read-a-kml-file-on-c-sharp-project-to-give-back-the-information)
var xDoc = XDocument.Load(pInputFile);
string xNs = "{" + xDoc.Root.Name.Namespace.ToString() + "}"; // don't know what the namespace is - don't care - just need to use it to get our data...
//array place to store info
List<ExtractPlacemark> MyExtracts = new List<ExtractPlacemark>();
foreach (XElement e1 in (from f in xDoc.Descendants(xNs + "Placemark") select f))
{
ExtractPlacemark thisPlaceMark = new ExtractPlacemark();
// get placemarks
foreach (XElement e2 in (from f in e1.Elements() where f.Name == xNs + "name" select f))
{
thisPlaceMark.Name = $"{e2.Value}";
break; // assume 1st is the one and only name element
}
// extract point (inside another element etc..)
foreach (XElement e2 in (from f in e1.Elements() where f.Name == xNs + "Point" select f))
{
foreach (XElement e3 in (from f in e2.Elements() where f.Name == xNs + "coordinates" select f))
{
string[] coordArray = $"{e3.Value}".Split(",");
//note: KML from google puts the LON first then the LAT - which is inverse of general convention... boot to the head
if (coordArray.Length > 0 && coordArray[0].IsNumeric()) { thisPlaceMark.Lon = coordArray[0].ToDouble(); }
if (coordArray.Length > 1 && coordArray[1].IsNumeric()) { thisPlaceMark.Lat = coordArray[1].ToDouble(); }
if (coordArray.Length > 2 && coordArray[2].IsNumeric()) { thisPlaceMark.Elev = coordArray[2].ToDouble(); }
break; // assume 1 point for a placemark...
}
}
// extract description
foreach (XElement e2 in (from f in e1.Elements() where f.Name == xNs + "description" select f))
{
thisPlaceMark.Description = StripBeforeTwoBreaks(e2.Value);
break; // again assume only 1 description...
}
// Image URL's (like points = it is inside another element - AND has wonky formatting...)
foreach (XElement e2 in (from f in e1.Elements() where f.Name == xNs + "ExtendedData" select f))
{
foreach (XElement e3 in (from f in e2.Elements() where f.Name == xNs + "Data" select f))
{
foreach (XElement e4 in (from f in e3.Elements() where f.Name == xNs + "value" select f))
{
string[] RawData = $"{e4.Value}".Split(" ");
foreach (string thisUrl in RawData)
{
if (thisUrl != "") { thisPlaceMark.ImageURLsAdd(thisUrl); }
}
break;
}
break; //again - first only
}
}
MyExtracts.Add(thisPlaceMark);
}
if (ExportJSON)
{
// generate hash ID's - then only export unique list...
List<string> ExtractHashes = new List<string>();
foreach (ExtractPlacemark thisPlaceMark in MyExtracts)
{
var thisHash = HashObj(thisPlaceMark);
if (!ExtractHashes.Exists(givenHash => (givenHash == thisHash)))
{
thisPlaceMark.ID = thisHash;
ExtractHashes.Add(thisHash);
}
}
string jsonExport = JsonSerializer.Serialize(MyExtracts.Where(placeMark => !string.IsNullOrEmpty(placeMark.ID)));
if (consoleInOut)
{
Console.Write(jsonExport);
}
else
{
File.WriteAllText(pOutputFile, jsonExport);
}
}
else
{
//header when doing text dump
if (consoleInOut) { Console.WriteLine("\n" + ExtractPlacemark.ExportHeader()); }
else { File.WriteAllText(pOutputFile, $"\n" + ExtractPlacemark.ExportHeader()); }
//the data
foreach (ExtractPlacemark thisPlace in MyExtracts)
{
//todo: make ExportLine configurable with arguements
if (consoleInOut) { Console.WriteLine(thisPlace.ExportLine()); }
else { File.AppendAllText(pOutputFile, thisPlace.ExportLine()); }
}
}
}
static void HelpUsage(string givenVerbage = null)
{
if (!string.IsNullOrEmpty(givenVerbage)) { Console.WriteLine(givenVerbage); }
Console.WriteLine("Usage: /KML:[file] /OUT:[file]");
Console.ReadKey();
Environment.Exit(-1);
}
static string getParam(string[] args, string givenPrefix, string defaultValue)
{
string result = defaultValue;
for (int n = 0; n < args.Length; n++)
{
if (args[n].Substring(0, givenPrefix.Length) == givenPrefix) // note: case sensitive
{
result = args[n].Substring(givenPrefix.Length, args[n].Length - givenPrefix.Length);
break;
}
}
return result;
}
public class ExtractPlacemark
{
public string ID { get; set; } // has to be set externally - sha256 hash of serialization of itself before ID is set.. (unless I think of a better way)
public string Name { get; set; }
public string Description { get; set; }
public double Lat { get; set; }
public double Lon { get; set; }
public double Elev { get; set; }
public List<string> ImageURLS { get; set; }
// non serializable parts of the class:
public void ImageURLsAdd(string givenImageURL)
{
if (this.ImageURLS == null) { this.ImageURLS = new List<string>(); }
this.ImageURLS.Add(givenImageURL);
}
public string LatLon() { return $"Lat:{Lat},Lon:{Lon}"; }
public string ExportLine(bool ShowHeader = false)
{
return $"{this.Name}{Delimiter}{this.Description}{Delimiter}{this.LatLon()}";
}
static public string ExportHeader()
{
return $"Name{Delimiter}Description{Delimiter}LatLon";
}
static public string Delimiter = "|";
}
private static string HashObj(object givenObject)
{
return HashString(JsonSerializer.Serialize(givenObject)); //question: did we just reinvent a wheel somehow?
}
//example from: https://www.c-sharpcorner.com/article/compute-sha256-hash-in-c-sharp/
private static string HashString(string givenString)
{
// Create a SHA256
using (SHA256 sha256Hash = SHA256.Create())
{
// ComputeHash - returns byte array
byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(givenString));
// Convert byte array to a string
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
builder.Append(bytes[i].ToString("x2"));
}
return builder.ToString();
}
}
private static string StripBeforeTwoBreaks(string givenString)
{
string breaks = "<br><br>";
string result = ""; // givenString;
if (givenString.Contains(breaks))
{
result = givenString.Substring(givenString.IndexOf(breaks) + breaks.Length);
// in case something put after - strip that out too
// obligatory rant: DARNY DARN-DARN YOU Keyhole & google for bastardizing a format like this - someone should be kicked in the crotch for this!!!
if (result.Contains(breaks))
{
result = result.Substring(0, result.IndexOf(breaks));
}
}
return result;
}
}
public static class StringExtensions
{
public static string Right(this string givenString, int rightFromEnd)
{
return givenString.Substring(givenString.Length - rightFromEnd, rightFromEnd);
}
public static bool IsNumeric(this string givenString)
{
bool result = false;
int thisInt = 0;
try { thisInt = int.Parse(givenString); } catch { }
if (thisInt != 0) result = true;
else
{
if (givenString.Replace("0", "") != "") { result = true; } //this needs to be tested thoroguhly..
}
return result;
}
public static double ToDouble(this string givenString)
{
double result = 0;
try { result = double.Parse(givenString); } catch { }
return result;
}
public static decimal ToDecimal(this string givenString)
{
decimal result = 0;
try { result = decimal.Parse(givenString); } catch { }
return result;
}
}
}