Calculate distance between two PFGeopoints in a NSPredicate for a Parse Query - objective-c

I have a special case when I want to do something like
let predicate = NSPredicate(format:"
DISTANCE(\(UserLocation),photoLocation) <= visibleRadius AND
DISTANCE(\(UserLocation),photoLocation" <= 10)"
var query = PFQuery(className:"Photo", predicate:predicate)
Basically, I want to get all photos that are taken within 10km around my current location if my current location is also within the photo's visible radius
Also, photoLocation and visibleRadius are two columns in the database, I will supply UserLocation as a PFGeoPoint.
Is it possible to achieve this? In my opinion, I don't think that I may call, for example, photoLocation.latitude to get a specific coordinate value. May I?
I'll appreciate you a lot if this can be achieved!!

I found this at the pares.com docs here is the link
let swOfSF = PFGeoPoint(latitude:37.708813, longitude:-122.526398)
let neOfSF = PFGeoPoint(latitude:37.822802, longitude:-122.373962)
var query = PFQuery(className:"PizzaPlaceObject")
query.whereKey("location", withinGeoBoxFromSouthwest:swOfSF, toNortheast:neOfSF)
var pizzaPlacesInSF = query.findObjects()
This code fetch you all the objects that are in a rectangle area defined by the swOfSF & neOfSF objectc, where seOfSF is in the south-west corner and neOfSF is in the north-east corner.
You can make some alterations to the code and get all the objects in rectangle area that your object is in middle
i would recommend that you don't use a radius, because it will take a lot of calculations. Instead use a rectangle area (like in the code i gave you).
just calculate what is the max/min longitude & max/min latitude from your position and fetch all the objects that are in between. you can read about how to fine the min/max longitude & latitude here Link

I managed to solve it using Parse Cloud Code, here is the quick tutorial
Parse.Cloud.define("latestPosts", function(request, response) {
var limit = 20;
var query = new Parse.Query("Post");
var userLocation = request.params.userLocation;
var searchScope = request.params.searchScope;
var afterDate = request.params.afterDate;
var senderUserName = request.params.senderUserName;
query.withinKilometers("Location", userLocation, searchScope);
query.greaterThan("createdAt", afterDate);
query.notEqualTo("senderUserName",senderUserName);
query.ascending("createdAt");
query.find({
success: function(results) {
var finalResults = results.filter(function(el) {
var visibleRadius = el.get("VisibleRadius");
var postLocation = el.get("Location");
return postLocation.kilometersTo(userLocation) <= visibleRadius;
});
if (finalResults.length > limit) {
var slicedFinalResults = results.slice(0, 20);
response.success(slicedFinalResults);
} else {
response.success(finalResults);
}
},
error: function() {
response.error("no new post");
}
});
});
The code above illustrate a basic example of how to use Cloud Code. Except, I have to make sure that all the returned image are in the union of user's search scope and photo's visible circle. There are more techniques such as Promises. But for my purpose, the code above should just suffice.

Related

FireStore Multiple where query

I have the following firestore query below. I am trying to perform multiple
where query on the Book collection. I want to filter by book name and book age range. However i am getting the following error
"uncaught error in onsnapshot firebaseError: cursor position is outside the range of the original query" can someone please advise.
const collectionRef = firebase.firestore().collection('Books')
collectionRef.where('d.details.BookType',"==",BookType)
collectionRef = collectionRef.where('d.details.bookage',"<=",age)
collectionRef = collectionRef.orderBy('d.details.bookage')
const geoFirestore = new GeoFirestore(collectionRef)
const geoQuery = geoFirestore.query({
center: new firebase.firestore.GeoPoint(lat, long),
radius: val,
});
geoQuery.on("key_entered",function(key, coords, distance) {
storeCoordinate(key,coords.coordinates._lat,coords.coordinates._long,newdata)
});
Internally geoFirestore gets its results by
this._query.orderBy('g').startAt(query[0]).endAt(query[1])
Laying it out sequentially, expanding your collectionRef, something like this is happening:
const collectionRef = firebase.firestore().collection('Books')
collectionRef.where('d.details.BookType',"==",BookType)
collectionRef = collectionRef.where('d.details.bookage',"<=",age)
collectionRef = collectionRef.orderBy('d.details.bookage')
collectionRef.orderBy('g').startAt(query[0]).endAt(query[1])
The problem happens because .startAt is referring to your first orderBy which is d.details.bookage, so it is doing start at the cursor where d.details.bookage is query[0].
Seeing that query[0] is a geohash, it translates to something like start at the cursor where d.details.bookage is w2838p5j0smt, hence the error.
Solution
There are two ways to workaround this limitation.
Wait for an update on Geofirestore which I think #MichaelSolati is already working on.
Sort the results after getting results from Geofirestore's onKey

Get the value of selectbox in cocoascript

I'm developing a sketch plugin. In the modal window I'm using to get user input there is a select. I can access the value of textField but I can't access value of the select.
Here is where I create the select:
var chooseFormatOptions = ['.png', '.jpg', '.pdf'];
var chooseFormatSelect = NSComboBox.alloc().initWithFrame(NSMakeRect(0, 250, viewWidth, 30));
chooseFormatSelect.addItemsWithObjectValues(chooseFormatOptions);
Here is where I try to get the combo box value
if (response == "1000"){
var projectName = projectField.stringValue();
var deviceName1 = firstDevicefield.stringValue();
var deviceDim1 = firstDimfield.stringValue();
var deviceName2 = secondDevicefield.stringValue();
var deviceDim2 = secondDimfield.stringValue();
var format = chooseFormatSelect.objectValues.indexOfSelectedItem(),
//var scale = chooseScaleOptions.stringValue();
//var pathOption = choosePathOptions.stringValue();
}
The error that it gives me when I run the plugin (if response == 1000) is: can't find variable chooseFormatSelect.
Do you know why I can get values of input fields (so it can find variables) but not that of the select one?
What about access text field 'text' variable while observing changes?
You may find this link helpfull (to add observe).
For NSComboBox follow this
Simply implement delegate then access value through following method

img.lockFocus is inaccessible via ObjC bridge?

I'm trying to create a purely JXA-ObjC approach to getting pixel colors from image paths. Here's what I have currently:
ObjC.import('Foundation')
ObjC.import('AppKit')
var c_filePath = $(picturePath)
var c_img = $.NSImage.alloc.initWithContentsOfFile(c_filePath)
if(c_img==$()){
return []
}
var c_point = $.NSMakePoint(x,y)
c_img.lockFocus() //Error - Undefined is not a function...?
var c_color = NSReadPixel(c_point)
c_img.unlockFocus() //Error - Undefined is not a function...?
c_img.release()
var r; var g; var b; var a
c_img.getRegGreenBlueAlpha($(r),$(g),$(b),$(a))
r = ObjC.unwrap(r)
g = ObjC.unwrap(g)
b = ObjC.unwrap(b)
a = ObjC.unwrap(a)
This code is heavily based off of the code found here.
However, as shown above c_img.lockFocus() is undefined according to JXA. Oddly I can get access to c_img.lockFocusFlipped(), however I'm not sure how to use this and/or if it can be used for the same purpose as lockFocus().
Is there an obvious problem here? Or is there a better way to get the pixel colour of an image?
Any help would be grateful.
It looks like I am too used to methods requiring parenthesis. TylerGaw however told me that this is not necessarily the case.
ObjC.import('Foundation')
ObjC.import('AppKit')
var c_filePath = $(picturePath)
var c_img = $.NSImage.alloc.initWithContentsOfFile(c_filePath)
if(c_img==$()){
return []
}
var c_point = $.NSMakePoint(x,y)
c_img.lockFocus
var c_color = NSReadPixel(c_point)
c_img.unlockFocus
c_img.release
appears to work as expected.

How to get exact location name with lat & long google map?

I want to get location name using latitude and longitude of user like any theater, restaurant, famous park, vacation place, shopping store. I am using Google map api but they only shows area name not any store name. How I get location name?
You can use Google Places API to get this information. Google Maps will not return store or place data based on latitude and longitude parameters.
I would enable your API key to work with Google Places API and then make a call to the getNearbyPlaces endpoint. The endpoint requires latitude, longitude, and radius (distance in meters in which the results must be found) parameters. Remember, this is still a query so your response will contain multiple results and by default, the results are ordered by popularity. I like to also specify a "type=establishment" as an optional parameter so I don't get abstract results like "route".
You can test this out in RapidAPI here. I've linked you directly to the getNearbyPlaces endpoint. Just fill in your apiKey, latitude, longitude, radius (I like to keep it around 20 meters for specificity), and any other optional parameters. Click "TEST function" to see a detailed JSON response. I'll show you a screenshot of what this looks like.
In this example, I looked up the latitude and longitude for Bob's Doughnuts. Boom! My first result was in fact, Bob's Doughnuts in San Francisco. RapidAPI also lets you generate a code snippet that you can copy and paste directly into your own code. You just have to click the "CODE" button above the response, sign in, and choose the language you're using. The code snippet provided from the above call looks like:
Hope this helps! One more thing.. Since the results are ordered by popularity, the first result might not always be the ideal result. The API provides a "rankBy" parameter, but I think Google is still working out some bugs with it. In the meantime, I would build a loop that finds the closest result item by distance. All you need to do is create a distance function using the Haversine formula. I'll build you a quick function! I used the haversine formula function from this post. Heres the code.
// Still using my Bob's Doughnuts example
const startLatitude = "37.7918904"; // your original query latitude
const startLongitude = "-122.4209966"; // your original query longitude
function closestResult(results) {
let closestResult = null;
let shortestDistance = null;
for (let i = 0; i < results.length; i++) {
let location = results[i].geometry.location;
let currentDistance =
getDistanceFromLatLonInKm(startLatitude, startLongitude, location.lat, location.lng);
if (shortestDistance === null) {
closestResult = results[i];
shortestDistance = currentDistance;
} else if (currentDistance < shortestDistance) {
closestResult = results[i];
shortestDistance = currentDistance;
}
}
return closestResult;
}
function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
var R = 6371; // Radius of the earth in km
var dLat = deg2rad(lat2-lat1); // deg2rad below
var dLon = deg2rad(lon2-lon1);
var a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.sin(dLon/2) * Math.sin(dLon/2)
;
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c; // Distance in km
return d;
}
function deg2rad(deg) {
return deg * (Math.PI/180);
}
Now your success callback can look like this:
Happy coding!

making a linegraph that shows population decay with dc.js and crossfilter

I am creating a dashboard in DC.js. One of the visualizations is a survival curve showing the percentage of survival on the y-axis and the time in weeks on the x-axis
Each record in the dataset contains a deathAfter column called recidiefNa. This shows the number of weeks after death occurred, and shows -99 for survival.
See sketches for example dataset and desired chart form:
I created this code to create the dimensions and groups and draw the desired chart.
var recDim = cf1.dimension(dc.pluck('recidiefNa'));//sets dimension
var recGroup = recDim.group().reduceCount();
var resDim = cf1.dimension(dc.pluck('residuNa'));
var resGroup = resDim.group().reduceCount();
var scChart = dc.compositeChart("#scStepChart");
scChart
.width(600)
.height(400)
.x(d3.scale.linear().domain([0,52]))
.y(d3.scale.linear().domain([0,100]))
.clipPadding(10)
.brushOn(false)
.xAxisLabel("tijd in weken")
.yAxisLabel("percentage vrij van residu/recidief")
.compose([
dc.lineChart(scChart)
.dimension(recDim)
.group(recGroup)
.interpolate("step-after")
.renderDataPoints(true)
.renderTitle(true)
.keyAccessor(function(d){return d.key;})
.valueAccessor(function(d){return (d.value/cf1.groupAll().reduceCount().value()*100);}),
dc.lineChart(scChart)
.dimension(resDim)
.group(resGroup)
.interpolate("step-after")
.renderDataPoints(true)
.colors(['orange'])
.renderTitle(true)
.keyAccessor(function(d){return d.key;})
.valueAccessor(function(d){return (d.value/cf1.groupAll().reduceCount().value()*100 );})
])
.xAxis().ticks(4);
scChart.render();
This gives the following result:
As you can see my first problem is that I need the line to extend until the y-axis showing x=0weeks and y=100% as the first datapoint.
So that's question number one: is there a way to get that line to look more like my sketch(starting on the y-axis at 100%?
My second and bigger problem is that it is showing the inverse of the percentage I need (eg. 38 instead of 62). This is because of the way the data is structured (which is somehting i rather not change)
First I tried changing the valueaccessor to 100-*calculated number. Which is obviously the normal way to solve this issue. However my result was this:
As you can see now the survival curve is a positive incline which is never possible in a survival curve. This is my second question. Any ideas how to fix this?
Ah, it wasn't clear from the particular example that each data point should be based on the last, but your comment makes that clear. It sounds like what you are looking for is a kind of cumulative sum - in your case, a cumulative subtraction.
There is an entry in the FAQ for this.
Adapting that code to your use case:
function accumulate_subtract_from_100_group(source_group) {
return {
all:function () {
var cumulate = 100;
return source_group.all().map(function(d) {
cumulate -= d.value;
return {key:d.key, value:cumulate};
});
}
};
}
Use it like this:
var decayRecGroup = accumulate_subtract_from_100_group(recGroup)
// ...
dc.lineChart(scChart)
// ...
.group(decayRecGroup)
and similarly for the resGroup
While we're at it, we can concatenate the data to the initial point, to answer your first question:
function accumulate_subtract_from_100_and_prepend_start_point_group(source_group) {
return {
all:function () {
var cumulate = 100;
return [{key: 0, value: cumulate}]
.concat(source_group.all().map(function(d) {
cumulate -= d.value;
return {key:d.key, value:cumulate};
}));
}
};
}
(ridiculous function name for exposition only!)
EDIT: here is #Erik's final adapted answer with the percentage conversion built in, and a couple of performance improvements:
function fakeGrouper(source_group) {
var groupAll = cf1.groupAll().reduceCount();
return {
all:function () {
var cumulate = 100;
var total = groupAll.value();
return [{key: 0, value: cumulate}]
.concat(source_group.all().map(function(d) {
if(d.key > 0) {
cumulate -= (d.value/total*100).toFixed(0);
}
return {key:d.key, value:cumulate};
}));
}
};
}