How can I convert a geoshape/geotrace/geopoint to GeoJSON? - leaflet

I Am using API to access spatial data collected using kobotoolbox. the api returns spatial data either as geoshape/geotrace/geopoint, but I need to covert that data to Geojson so that I can display them on my map using leaflet.
here is sample string of geoshape '-6.725577650887138 39.10606026649475 0.0 0.0;-6.72631550943841 39.10506717860699 0.0 0.0;-6.727484560110362 39.10561669617891 0.0 0.0;-6.727484560110362 39.10561669617891 0.0 0.0;-6.725577650887138 39.10606026649475 0.0 0.0;'
thanks a lot in advance!

That "geoshape" doesn't look like any well-known vector data format but rather looks like a string of ;-separated 4-element vectors, which in turn are space-separated numbers.
Some naïve parsing is in order.
Start by splitting the string into an array of strings with String.prototype.split():
let geoshape = '-6.725577650887138 39.10606026649475 0.0 0.0;-6.72631550943841 39.10506717860699 0.0 0.0;-6.727484560110362 39.10561669617891 0.0 0.0;-6.727484560110362 39.10561669617891 0.0 0.0;-6.725577650887138 39.10606026649475 0.0 0.0;';
let points = geoshape.split(';');
Now points is an Array of Strings. We can turn each of those strings into an Array of Numbers by providing a function that splits it by spaces:
function spaceSeparatedStringToArray(str) {
return str.split(' ');
}
...and another function that coerces a String containing the textual representation of a number into a Number, like...
function stringToNumber(str) {
return Number(str);
}
...and now let's put in some Array.prototype.map() magic and some method chaining magic, to make a function in which the input is a space-separated string and the output is an Array of Numbers:
function stringToNumber(str) { return Number(str); }
function spaceSeparatedStringToArrayOfNumbers(str) {
return str.split(' ').map(stringToNumber);
}
Sometimes it's nicer to define those auxiliary functions passed to map() (or reduce() or filter() or etc) as anonymous lambda-functions, like:
function spaceSeparatedStringToArrayOfNumbers(str) {
return str.split(' ').map(function(str){ return Number(str) });
}
You can use arrow function syntax instead if you want:
function spaceSeparatedStringToArrayOfNumbers(str) {
return str.split(' ').map(str=>Number(str) );
}
And since we're using Number as a function, we can directly use that as the parameter of map():
function spaceSeparatedStringToArrayOfNumbers(str) {
return str.split(' ').map(Number);
}
Now that we have that function, let's go back to the beginning, and apply that function to each of the ;-separated substrings:
function spaceSeparatedStringToArrayOfNumbers(str) {
return str.split(' ').map(Number);
}
let geoshape = '-6.725577650887138 39.10606026649475 0.0 0.0;-6.72631550943841 39.10506717860699 0.0 0.0;-6.727484560110362 39.10561669617891 0.0 0.0;-6.727484560110362 39.10561669617891 0.0 0.0;-6.725577650887138 39.10606026649475 0.0 0.0;';
let points = geoshape.split(';');
let vectors = points.map(spaceSeparatedStringToArrayOfNumbers);
Let's change that to add even more lambda and method chaining and arrow syntax:
let vectors = geoshape.split(';').map((str)=>str.split(' ').map(Number));
Also, since the GeoJSON format expects each coordinate as a 2-component vector instead of a 4-component vector, let's map each vector again to keep only the first two components. This uses array destructuring:
let vectors = geoshape
.split(';')
.map((str)=>str.split(' ').map(Number))
.map(([x,y,w,z])=>[x,y]);
See? I defined a lambda-function that takes a single argument, assumes it's an array (of numbers) with length 4, destructures the array, then returns a new array containing the first two elements. It can also work like:
let coords = geoshape
.split(';')
.map((str)=>str.split(' ').map(Number))
.map(([x,y])=>[x,y]);
Your sample data ends with a ;, and that causes an empty string, so I'll filter that out as well:
let coords = geoshape
.split(';')
.filter(str=> str!=='')
.map((str)=>str.split(' ').map(Number))
.map(([x,y])=>[x,y]);
I'm gonna assume that you're working in Dar es-Salaam and not in Badajoz, so let's flip latitude and longitude (see https://macwright.com/lonlat/ as well) by swapping x and y in ([x,y])=>[y,x]:
let coords = geoshape
.split(';')
.filter(str=> str!=='')
.map((str)=>str.split(' ').map(Number))
.map(([x,y])=>[y,x]);
Now head over to geojson.org and read RFC 7946 to see how GeoJSON data structures are specified. We already have the coordinates of a LineString geometry, so all it needs is some wrapping:
let geojsonFeature = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": coords
}
};
If your sample dataset is meant to be a polygon with an outer hull and no inner hulls (read OGC SFA to know what "hull" means in this context), then you can wrap the coordinates in an extra inline array and specify Polygon as the geometry type:
let geojsonFeature = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [ coords ]
}
};
Now you can feed it to L.geoJson, e.g.
let line = L.geoJson(geojsonFeature).addTo(map);
And finally, I'm gonna compact everything together just for the sake of it:
let geoshape = '-6.725577650887138 39.10606026649475 0.0 0.0;-6.72631550943841 39.10506717860699 0.0 0.0;-6.727484560110362 39.10561669617891 0.0 0.0;-6.727484560110362 39.10561669617891 0.0 0.0;-6.725577650887138 39.10606026649475 0.0 0.0;';
let leafletLine = L.geoJson({
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": geoshape
.split(';')
.filter(str=> str!=='')
.map((str)=>str.split(' ').map(Number))
.map(([x,y])=>[y,x])
}
}).addTo(map);
See a working example here. And please, please, don't copy-paste code blindly. Do read the linked documentation and resources, and understand what's going on.

Related

Left side of mutating operator isn't mutable: 'gpa' is a 'let' constant

I am currently struggling with an error for a homework assignment in my coding class. We are creating a loop that loops through an array of gpa values and then adds it to a variable named totalGradePoints. The problem is that I am coming across an error when the loop runs:
Left side of mutating operator isn't mutable: 'gpa' is a 'let' constant
The error is on this line:
var totalGradePoints = Double()
for gpa in gpaValues {
let averageGPA: Double = gpa += totalGradePoints
}
Here is my full code:
//: Playground - noun: a place where people can play
import UIKit
// You are the university registrar processing a transfer student's transcripts that contains grades that are a mix of letters and numbers. You need to add them to our system, but first you need to convert the letters into grade points.
// Here's an array of the student's grades.
var transferGrades: [Any] = ["C", 95.2, 85, "D", "A", 93.23, "P", 90, 100]
// To prepare for converting the letters to numerical grades, create a function that returns a double, inside which you create a switch that will convert an A to a 95, B to 85, C to 75, D to 65, , P (for passing) to 75. Everything else will be a zero.
func gradeConverter(letterGrade: String) -> Double {
switch letterGrade {
case "A":
return 95
case "B":
return 85
case "C":
return 75
case "D":
return 65
case "P":
return 75
default: // Is this where everything else is zero?
return 0
}
}
// Create a new array called convertedGrades that stores doubles.
var convertedGrades: [Double] = [98.75, 75.5, 60.0, 100.0, 82.25, 87.5]
// Loop through the transferGrades array, inspecing each item for type and sending strings (your letter grades) to the function you just made and storing the returned double in your convertedGrades array. If your loop encounters a double, you can place it directly into the new array without converting it. It it encounters an int, you will need to convert it to a double before storing it. Print the array. (You may notice that some of your doulbes are stored with many zeros in the decimal places. It's not an error, so you can ignore that for now)
for grade in transferGrades {
if let gradeAsString = grade as? String {
gradeConverter(letterGrade: gradeAsString)
} else if let gradeAsDouble = grade as? Double {
transferGrades.append(gradeAsDouble)
} else if let gradeAsInt = grade as? Int {
Double(gradeAsInt)
transferGrades.append(gradeAsInt)
}
}
print(transferGrades)
// Now that we have an array of numerical grades, we need to calculate the student's GPA. Create a new array called GPAValues that stores doubles.
var gpaValues: [Double] = [2.5, 3.0, 4.0, 3.12, 2.97, 2.27]
// Like with the letter conversion function and switch you created before, make a new function called calculateGPA that takes a double and returns a double. Inside your function, create another switch that does the following conversion. Grades below 60 earn zero grade points, grades in the 60s earn 1, 70s earn 2, 80s earn 3, and 90s and above earn 4.
func calculateGPA(gpaValue: Double) -> Double {
switch gpaValue {
case 0..<59:
return 0
case 60...69:
return 1
case 70...79:
return 2
case 80...89:
return 3
case 90..<100:
return 4
default:
return 0
}
}
// Loop through your convertedGrades array and append the grade point value to the GPAValues array. Because your calculateGPA function returns a value, you can use it just like a varialbe, so rather than calculate the grade points and then put that varialbe in your append statement, append the actual function. i.e. myArray.append(myFunction(rawValueToBeConverted))
for gpa in gpaValues {
gpaValues.append(calculateGPA(gpaValue: gpa))
}
// Finally, calculate the average GPA by looping through the GPA and using the += operator to add it to a variable called totalGradePoints. You may need to initialize the variable before using it in the loop. i.e. var initialized = Double()
var totalGradePoints = Double()
for gpa in gpaValues {
let averageGPA: Double = gpa += totalGradePoints
}
// Count the number of elements in the array (by using the count method, not your fingers) and store that number in a variable called numberOfGrades. Pay attention to creating your variables with the right types. Swift will tell you if you're doing it wrong.
var numberOfGrades: Int = gpaValues.count
// Divide the totalGradePoints by numberOfGrades to store in a variable called transferGPA.
var transferGPA: Double = Double(totalGradePoints) / Double(numberOfGrades)
// Using code, add one numerical grade and one letter grade to the transferGrades array that we started with (i.e. append the values rather than manualy writing them into the line at the beginning of this file) and check that your transferGPA value updates. You'll need to append the new grades on the line below the existing transferGrades array so that your changes ripple through the playground.
transferGrades.append(97.56)
transferGrades.append("B")
averageGPA must be define using the var keyword to make it mutable later on when summing up the values.
var averageGPA: Double = 0
for gpa in gpaValues {
averageGPA += gpa
}
averageGPA = averageGPA / Double(gpaValues.count)
Recall the average is calculated by summing up the score and dividing the number of scores.
Defining something with let means that the following will be a constant.
let answer: Int = 42
answer = 43 /* Illegal operation. Cannot mutate a constant */
Left side of mutating operator isn't mutable: 'gpa' is a 'let' constant
The problem is that gpa is a constant, you can't modify its value. And the "+=" operator means "increase gpa's value by totalGradePoints", it is trying to increase the value of gpa. What you probably mean to do is make averageGPA equal the sum of gpa and totalGradePoints. For that you would do this:
let averageGPA: Double = gpa + totalGradePoints

Why doesn't vDSP_maxv find the max value in the vector?

vDSP_maxv is not assigning the max value to output in the code below.
I expected the last line to print 2, but instead it prints something different each time, usually a very large or small number like 2.8026e-45
I've read this tutorial, the documentation, and the inline documentation in the header file for vDSP_maxv, but I don't see why the code below isn't producing the expected result.
Making numbers an UnsafePointer instead of an UnsafeMutablePointer didn't work, nor did a number of other things I've tried, so maybe I'm missing something fundamental.
import Accelerate
do {
// INPUT - pointer pointing at: 0.0, 1.0, 2.0
let count = 3
let numbers = UnsafeMutablePointer<Float>
.allocate(capacity: count)
defer { numbers.deinitialize() }
for i in 0..<count {
(numbers+i).initialize(to: Float(i))
}
// OUTPUT
var output = UnsafeMutablePointer<Float>
.allocate(capacity: 1)
// FIND MAX
vDSP_maxv(
numbers,
MemoryLayout<Float>.stride,
output,
vDSP_Length(count)
)
print(output.pointee) // prints various numbers, none of which are expected
}
You are mistaking the usage of the stride parameter to vDSP_maxv.
You need to pass the number of elements consisting a single stride, not the number of bytes.
vDSP_maxv(::::)
*C = -INFINITY;
for (n = 0; n < N; ++n)
if (*C < A[n*I])
*C = A[n*I];
In the pseudo code above, I represents the stride parameter, and you see giving 4 (MemoryLayout<Float>.stride) to I would generate indexes exceeding the bound of A (your numbers).
Some other parts fixed to fit my preference, but the most important thing is the second parameter for vDSP_maxv:
import Accelerate
do {
// INPUT - pointer pointing at: 0.0, 1.0, 2.0
let numbers: [Float] = [0.0, 1.0, 2.0]
// OUTPUT
var output: Float = Float.nan
// FIND MAX
vDSP_maxv(
numbers,
1, //<- when you want to use all elements in `numbers` continuously, you need to pass `1`
&output,
vDSP_Length(numbers.count)
)
print(output) //-> 2.0
}

How can I search through this array for a specific value then return the other value in the tuple?

I have a large array of (x,y) pairs:
P =
[
(0.0, 500000.09999999998),
(0.001, 18.332777589999999),
(0.002, 18.332221480000001),
(0.0030000000000000001, 18.331665000000001),
(0.0040000000000000001, 18.331108140000001),
(0.0050000000000000001, 18.33055092),
(0.0060000000000000001, 18.32999332),
...
]
I now need to use this in my code. I need to search for a specific x-value and, if the x-value exists, return its corresponding y-value.
Note: If there is a better format I could put my (x,y) pairs in, please feel free to let me know. For example, 2 separate arrays where one holds the x-values and the other holds the y-values. Then I could use the index to find the corresponding y-value or something.
Edit:
A user made a very good point in the comments: how can I reliably compare x == 0.001?
The way I will be using my code is this: I am evaluating a function f(x) at values of x. However, if at a particular value of x there is a y value in the P array, then I need to do an extra subtraction calculation (the details of which are not too important here). The problem, then, is that what if I pass the x value 0.001 in there and the P array does not have a correpsonding y value, but it does have one for 0.001000000009?? Then the code will say there is no value, but in reality it is reasonably close to the intended x value.
I'd suggest to let your array to be an array of CGPoints. It's simply:
A structure that contains a point in a two-dimensional coordinate
system.
However, if you want to get the y values based on searching the x:
let myArray = [
(0.0, 500000.09999999998),
(0.001, 18.332777589999999),
(0.002, 18.332221480000001),
(0.0030000000000000001, 18.331665000000001),
(0.0040000000000000001, 18.331108140000001),
(0.0050000000000000001, 18.33055092),
(0.0060000000000000001, 18.32999332),
]
// this array should contains y values for a given x value
// for example, I search the x value of 0.0
let yValues = myArray.filter { $0.0 == 0.0 }.map { $0.1 }
print(yValues) // [500000.09999999998]
Hope this helped.
A good way of doing this is by declaring this function:
func getValueFromTuples(tupleArr:[(Double,Double)],n:Double)->Double?{
for tuple in tupleArr{
if tuple.0 == n{
return tuple.1
}
}
return nil
}
Then, you can use it like this:
var tupleArray: [(Double,Double)] = [(1.0, 12.0),(2.0,23.0),(3.0,34.0),(4.0,45.0),(5.0,56.0)]
var x:Double = 1.0
print(getValueFromTuples(tupleArr: tupleArray,n:x) ?? "No value found") // 12.0
Where the n argument is the value to be found, the tuple is the key-value pair formed by the numbers and getValueFromTuples returns the value y if x has been found, else nil.
This returns "No value found" if the value does not exist in the array of tuples.
Hope this helps!
Your x value all seem to increase by 0.001. If that is the case, you could also calculate the index and return the y value at this index. This would be a lot more efficient.
func calculateIndex(forX x: Double) -> Int {
let increase = 0.001
return Int(x/0.001)
}
You can use the find method to find the index of the x value and then return the y value. I would multiply your values by 1000 and then compare the Int instead of comparing Double.
func findYValue(forX x: Double) -> Double? {
let multiply = 1000
let x = Int(multiply*x)
if let index = array.index(where: { Int($0.0 * multiply) == x }) {
return array[index].1 //found the y value
}
return nil //x is not in the array
}
Instead of using tuples, I would personally use CGPoint. The class has an x and a y property, which makes your code more readable.
Microsoft gives a very thorough explanation of how to compare 2 doubles. The basic premise is that you need to define a certain level of tolerance. The article the explores how to pick a good tolerance in most cases.
Here's code translated to Swift:
func areEqual(_ lhs: Double, _ rhs: Double, units: Int = 3) -> Bool {
let lValue = Int64(bitPattern: lhs.bitPattern)
let rValue = Int64(bitPattern: rhs.bitPattern)
let delta = lValue - rValue
return abs(delta) <= Int64(units)
}
Test:
var n = 0.0
for _ in 0..<10 {
n += 0.1
}
// n should equal 1 but it does not
print(n == 1.0) // false
print(areEqual(1.0, n)) // true
Back to your problem, it becomes straight forward after you defined how to test for equality in 2 doubless:
let x = 0.003
if let y = p.first(where: { areEqual($0.0, x) })?.1 {
print(y)
}

binary operator * cannot be applied to operands of type Int and Double

I'm trying to build a simple Swift app to calculate VAT (Value Added taxes = 20%).
func taxesFree(number: Int) -> Double {
var textfield = self.inputTextField.text.toInt()!
let VAT = 0.2
var result = textfield * VAT
return result
}
For some reason I keep getting
Binary operator * cannot be applied to operands of type Int and Double
on the line
var result = textfield * VAT
You should convert one type to the other one so both variable should be the same types:
var result: Double = Double(textfield) * VAT
It's because you're trying to multiply an Int (textfield) with a Double (VAT). Because with such an operation you could lose the precision of the double Swift doesn't allow to convert one to the other so you need to explicitly cast the Int to a Double ...
var result = Double(textfield) * VAT
The problem here is that the statement given is literally true, because Swift is strongly typed and doesn't coerce implicitly. Just had a similar case myself with "binary operator '-' cannot be applied to operands of type 'Date' and 'Int'".
If you write:
var result = 10 * 0.2
...that's fine, but if you write:
var number = 10
var result = number * 0.2
...that's not fine. This is because untyped explicit values have an appropriate type selected by the compiler, so in fact the first line is taken as being var result = Double(10) * Double(0.2). After all, as a human being you might mean 10 to be floating-point or an integer - you normally wouldn't say which and would expect that to be clear from context. It might be a bit of a pain, but the idea of strong types is that after the code is parsed it can only have one valid compiled expression.
In general you would build a new value using the constructor, so var result = Double(textfield) * VAT in your case. This is different from casting (textfield as Double) because Int is not a subclass of Double; what you are doing instead is asking for a completely new Double value to be built at runtime, losing some accuracy if the value is very high or low. This is what loosely typed languages do implicitly with pretty much all immediate values, at a small but significant time cost.
In your specific case, it wasn't valuable to have an Int in the first place (even if no fraction part is possible) so what you needed was:
func taxesFree(number: Int) -> Double {
var textfield = Double(self.inputTextField.text)!
let VAT = 0.2
var result = textfield * VAT
return result
}
In my case it was just casting to CGFloat:
self.cnsMainFaqsViewHight.constant = CGFloat(self.mainFaqs.count) * 44.0
You can convert like
var result: Double = Double(textfield)
I was misunderstanding the Closed Range Operator in Swift.
You should not wrap the range in an array: [0...10]
for i in [0...10] {
// error: binary operator '+' cannot be applied to operands of type 'CountableClosedRange<Int>' and 'Int'
let i = i + 1
}
for i in 0...10 {
// ok!
let i = i + 1
}
The range is a collection that can itself be iterated. No need to wrap it in an array, as perhaps you would have in Objective-C.
0...3 -> [0, 1, 2, 3]
[0...3] -> [[0, 1, 2, 3]]
Once you realize your object is a nested collection, rather than an array of Ints, it's easy to see why you cannot use numeric operators on the object.
This worked for me when I got the same error message in Playground:
func getMilk(howManyCartons: Int){
print("Buy \(howManyCartons) cartons of milk")
let priceToPay: Float = Float(howManyCartons) * 2.35
print("Pay $\(priceToPay)")
}
getMilk(howManyCartons: 2)

INFINITY in Swift Lang

According to Apple's documentation, Swift doesn't support preprocessor directives. In C/Objective-c the "INFINITY" definition is very useful for some checks.
So, How do I get a number that never is less that another?
There is already buildin infinity and also a check function. And you could also directly compare them with <.
var infinity = Double.infinity
var isInfinite = infinity.isInfinite
var someDouble = 234432.0
if someDouble < infinity {
println("Less than")
} else {
println("Small than")
}
// And the answer is Less than.
For integer values, you should use Int.max.
var highestNumber = Int.max
//if you need negative infinity
var lowestNumber = Int.min
Using NSIntegerMax instead of Int.max or -1 * NSIntegerMax instead of Int.min is equivalent, but less pretty. (Thanks #Charlesism)
Perhaps you can try finite, for example,
let x:CDouble = 0.1
finite(x) // which return a CInt
For Float values,
import UIKit
typealias Space = Float
var MaxSpaceSize = Space.infinity
var space:Space = 1100
space = space * 2