How to pass user's browser geolocation (from javascript) to rails? - javascript

I'm currently setting the user's coordinates (latitude and longitude) using request.location.coordinates, but the location is not accurate enough. I want to use the user's browser geolocation instead.
How can I pass the latitude and longitude gotten via javascript into the rails user_coordinates variable? I've read that you should use AJAX for this, but I am a beginner and don't know how AJAX works. In particular, I've seen code snippets in various places but I don't know what file to even put them in to try it.
Update: It says here that I should use turbo streams for Ajax in Rails 7 - can anyone help me understand how this works? https://www.reddit.com/r/rails/comments/vfmymj/how_can_i_implement_ajax_in_rails_7/
controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_user_coordinates
def set_user_coordinates
if Rails.env.production?
#user_coordinates = request.location.coordinates
end
end
end
javascript/controllers/geolocation_controller.js
import { Controller } from '#hotwired/stimulus';
export default class extends Controller {
static targets = ['park'];
connect() {
window.navigator.geolocation.getCurrentPosition((position) => {
this.setUserCoordinates(position.coords);
this.setDistanceText();
})
}
setUserCoordinates(coordinates) {
this.element.dataset.latitude = coordinates.latitude;
this.element.dataset.longitude = coordinates.longitude;
}
getUserCoordinates() {
return {
latitude: this.element.dataset.latitude,
longitude: this.element.dataset.longitude,
};
}
setDistanceText() {
this.parkTargets.forEach((parkTarget) => {
let distanceFrom = getDistance(
this.getUserCoordinates(),
{ latitude: parkTarget.dataset.latitude,
longitude: parkTarget.dataset.longitude },
);
parkTarget.querySelector('[data-distance-away]').innerHTML =
`${Math.round(convertDistance(distanceFrom, 'km'))}`;
});
}
}
Usage context: I am using the location data to sort parks based on distance from the user. I'm using Ransack to sort by distance, and distance is set via the Geocoder near method, which uses the user_coordinates variable to calculate distances:
controllers/parks_controller.rb
class ParksController < ApplicationController
def index
#parks = #q.result(distinct: true).includes(:visited_users, :favorited_users).near(#user_coordinates, 100000000).paginate(page:params[:page], :per_page => 24)
end
end

To pass the latitude and longitude from JavaScript to Rails, you can make an AJAX request to a Rails endpoint with the coordinates as data. Here's how you could modify the setUserCoordinates method to make the AJAX request:
setUserCoordinates(coordinates) {
this.element.dataset.latitude = coordinates.latitude;
this.element.dataset.longitude = coordinates.longitude;
fetch('/set_coordinates', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
latitude: coordinates.latitude,
longitude: coordinates.longitude
})
});
}
In your Rails routes.rb file, add a new endpoint for the AJAX request:
Rails.application.routes.draw do
post '/set_coordinates', to: 'geolocation#set_coordinates'
end
Then create a new controller in Rails to handle the AJAX request:
class GeolocationController < ApplicationController
def set_coordinates
session[:latitude] = params[:latitude]
session[:longitude] = params[:longitude]
head :ok
end
end
Finally, in your ApplicationController, modify the set_user_coordinates method to use the latitude and longitude from the session:
def set_user_coordinates
if Rails.env.production?
#user_coordinates = [session[:latitude], session[:longitude]]
end
end
With these changes, the user's latitude and longitude will be sent to the server via AJAX and stored in the session, which can then be used in your Rails controllers.
Regarding Turbo Streams, it is a new feature in Rails 7 that allows for real-time communication between the server and client. However, you do not need it for the above solution.

Related

.NET Core - Load View Components asnychronous (client side) via Wrapper

To optimize the response time of our web application I would like to load View Components in .NET Core 2.2 asynchronous from client side without Javascript. Of course you could achieve this by loading them with an AJAX Call and then render the view from a Controller.
But I would like to keep the intellisense and work with tags in the code, so you can invoke a view component with a tag like this multiple times in different places and don't need to create multiple ajax posts in JS:
<vc:free-text ftx-code="STARTPAGE" obj-ref="null" custom-para="null"></vc:free-text>
I tried to load the view component via TagHelper, but apparently they also are rendered synchronously (only server side asynchronous):
[HtmlTargetElement("widget", Attributes = WidgetNameAttributeName)]
public class WidgetTagHelper : TagHelper
{
private readonly IViewComponentHelper _viewComponentHelper;
public WidgetTagHelper(IViewComponentHelper viewComponentHelper)
{
_viewComponentHelper = viewComponentHelper;
}
.....
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
((IViewContextAware)_viewComponentHelper).Contextualize(ViewContext);
var content = await _viewComponentHelper.InvokeAsync(typeof(FreeTextViewComponent), new { ftxCode = FTXCode, objRef = OBJRef, customPara = CustomPara });
output.Content.SetHtmlContent(content);
}
}
Does anyone know an approach (wrapper, TagHelper, etc.?) where I first load the ViewComponent with an "empty" modal and when it's rendered it makes an client side asynchronous callback to fetch the actual data, like described in this post?
https://github.com/aspnet/AspNetCore.Docs/issues/7914#issuecomment-441280663
View components and tag helpers cannot be accessed directly. They aren't part of the request pipeline and don't respond to anything. The best you can do is create a view that happens to utilize the component or tag helper, and then return that view from an action, which is what you would actually make the request to.
Generally speaking, for this type of stuff, you'd be better served with a client-side library like Vue, Angular, etc. and create client-side components via those libraries. Then, your server acts as an API, returning JSON data that you feed into those components.
I ended up by reloading them via an AJAX call, but only one for all rendered views. First, I load the ViewComponent with a parameter "async" set to true (it only fetches data, when async is set to "false"):
public async Task<IViewComponentResult> InvokeAsync(string ftxCode, string objRef = "", List<FTXPara> customPara = null, bool async = false)
{
if (async)
{
ViewData["ftxCode"] = ftxCode;
ViewData["async"] = async;
return View(new GetFreeTextResponse());
}
List<CMailCustomParameter> para = new List<CMailCustomParameter>();
if (customPara != null)
{
foreach (var p in customPara)
{
para.Add(new CMailCustomParameter { ParameterName = p.Name, ParameterValue = p.Value });
}
}
var resp = await GetItemsAsync(ftxCode, objRef, para);
return View(resp);
}
private async Task<GetFreeTextResponse> GetItemsAsync(string ftxCode, string objRef, List<CMailCustomParameter> para)
{
return await FreeTextService.GetFreeText(new GetFreeTextRequest { FTX_Code = ftxCode, ObjRef = objRef, CustomParameters = para });
}
Then I loop through every rendered "empty" View Component and load the View Component with the actual data from a controller (this time "async" is false"):
$('.view-component').each(function (i, obj) {
var that = this;
var id = $(this).attr('id');
var test = $(this).data();
console.log($(this).data('async'));
if ($(this).data('async')) {
$.ajax({
url: "/VC/Get" + "FreeText" + "ViewComponent",
type: 'POST',
data: $(this).data(),
contentType: 'application/x-www-form-urlencoded',
success: function (data) {
$(that).html(data);
},
error: function (xhr, status, error) {
console.log(xhr.status + " - " + error);
}
});
}
});

Customize loopback model

How do i customize a PersistedModel in loopback ? Let's say i have two models Post and Comment. A Post hasMany Comment but it can have at most 3 comments. How can i implement that without using hooks? Also i need to do it inside a transaction.
I'm coming from java and this is how i would do that:
class Post {
void addComment(Comment c) {
if(this.comments.size() < 3)
this.comments.add(c)
else
throw new DomainException("Comment count exceeded")
}
}
then i would write a service ...
class PostService {
#Transactional
public void addCommentToPost(postId, Comment comment) {
post = this.postRepository.findById(postId);
post.addComment(comment)
this.postRepository.save(post);
}
}
I know i could write something like:
module.exports = function(app) {
app.datasources.myds.transaction(async (models) => {
post = await models.Post.findById(postId)
post.comments.create(commentData); ???? how do i restrict comments array size ?
})
}
i want to be able to use it like this:
// create post
POST /post --> HTTP 201
// add comments
POST /post/id/comments --> HTTP 201
POST /post/id/comments --> HTTP 201
POST /post/id/comments --> HTTP 201
// should fail
POST /post/id/comments --> HTTP 4XX ERROR
What you are asking here is actually one of the good use cases of using operation hooks, beforesave() in particatular. See more about it here here
https://loopback.io/doc/en/lb3/Operation-hooks.html#before-save
However, I'm not so sure about the transaction part.
For that, I'd suggest using a remote method, it gives you complete freedom to use the transaction APIs of loopback.
One thing to consider here is that you'll have to make sure that all comments are created through your method only and not through default loopback methods.
You can then do something like this
// in post-comment.js model file
module.exports = function(Postcomment){
Postcomment.addComments = function(data, callback) {
// assuming data is an object which gives you the postId and commentsArray
const { comments, postId } = data;
Postcomment.count({ where: { postId } }, (err1, count) => {
if (count + commentsArray.length <= 10) {
// initiate transaction api and make a create call to db and callback
} else {
// return an error message in callback
}
}
}
}
You can use validateLengthOf() method available for each model as part of the validatable class.
For more details refer to Loopback Validation
i think i have found a solution.
whenever you want to override methods created by model relations, write a boot script like this:
module.exports = function(app) {
const old = app.models.Post.prototype.__create__comments;
Post.prototype.__create__orders = function() {
// **custom code**
old.apply(this, arguments);
};
};
i think this is the best choice.

Calling Rails controller data through JS AJAX call

I'm making a rails app using this code in the controller to call an API- I initially call the inventories endpoint, then make separate calls to two other id endpoints store_id, product_id to grabs specifics pieces of data linked to the inventories. This data gets passed into a hash that becomes '#inventories / transformed results':
class InventoriesController < ApplicationController
def index
response = Typhoeus.get("http://lcboapi.com/inventories")
parsed_json = JSON.parse(response.body)
transformed_results = []
parsed_json["result"].each do |inventory|
transformed_results.push(
{
product_name: product_lookup(inventory["product_id"]),
store_name: store_lookup(inventory["store_id"]),
quantity: inventory["quantity"],
}
)
end
#inventories = transformed_results
end
private
def store_lookup(store_id)
response = Typhoeus.get("http://lcboapi.com/stores/#{store_id}")
parsed_json = JSON.parse(response.body)
return parsed_json["result"]["name"]
end
def product_lookup(product_id)
response = Typhoeus.get("http://lcboapi.com/products/#{product_id}")
parsed_json = JSON.parse(response.body)
return parsed_json["result"]["name"]
end
end
My question is how best to get my json hash through AJAX into a form I can pass through and iterate in assets/javascript.
I am aware I can build it into the view (html.erb) and have done so, but I want to make my data interact with DOM elements.
Note: I've tried doing a simple console log to show the json data in the console as a test, with no response. I'm okay with using jQuery until I get comfortable with React, but I'm not sure how to grab my #inventories data from 'assets/javascript/inventories.js' - for instance, if I wanted to grab something from a csv data bases I'd use the following, but in this case it's not quite there:
document.addEventListener('DOMContentLoaded', function () {
console.log("ready!");
$.ajax({
url: "/inventories",
method: "GET"
}).done(function(data){
var products = []
data.forEach(function(item){
products.push(item.product_name).toString();
console.log(products);
});
});
})
In one of your js files (in assets/javascript), you'll need to do something like:
storeLookupResults = $.ajax({
url: "/inventories.js",
type: 'GET'
})
storeLookupResults.success (data) =>
# do stuff with your results
# 'data' will contain your json
NOTE: I made up the route, so you'll need to make sure you use a real route
Then, in your InventoriesController, modify index to something like:
def index
response = Typhoeus.get("http://lcboapi.com/inventories")
parsed_json = JSON.parse(response.body).with_indifferent_access
#inventories = parsed_json[:result].map do |inventory|
{
product_name: product_lookup(inventory[:product_id]),
store_name: store_lookup(inventory[:store_id]),
quantity: inventory[:quantity],
}
end
respond_to do |format|
format.html
format.json {render json: #inventories, status: :ok}
end
end
Note that .map returns an array. So, you don't have to do:
transformed_results = []
parsed_json["result"].each do |inventory|
transformed_results.push(
{
product_name: product_lookup(inventory["product_id"]),
store_name: store_lookup(inventory["store_id"]),
quantity: inventory["quantity"],
}
)
end
#inventories = transformed_results
Also note that I did:
parsed_json = JSON.parse(response.body).with_indifferent_access
It's a purely stylistic preference. I like using symbols instead of strings.

Neo4jClient with keylines

Introduction:
I am a newbie to Neo4jClient so forgive me if i ask something that has been asked before. But i have been stuck on this forever.
What i Am trying to do:
I am trying to connect Neo4j with Keylines using .NET.
Cause:
First, i used Neo4j's REST with Jquery AJAX to do Return (*) which returned everything including data and URI's of self, start, end, all_Relationships etc.
data: Object
end: "http://localhost:7474/db/data/node/93"
extensions: Object
metadata: Object
properties: "http://localhost:7474/db/data/relationship/4019/properties"
property: "http://localhost:7474/db/data/relationship/4019/properties/{key}"
self: "http://localhost:7474/db/data/relationship/4019"
start: "http://localhost:7474/db/data/node/6"
type: "SENT"
Hence i was able to use self as a ID for keylines Node and start and end URI's as Keyline's Link id1 and id2.
function queryNeo4j(query) {
var request = $.ajax({
type: "POST",
url: "http://localhost:7474/db/data/cypher",
contentType: "application/json",
data: JSON.stringify({ "query": query, "params": {} }),
success: afterQueryNeo4j
});
};
function afterQueryNeo4j(json) {
console.log(json); // returned data from ajax call
makeNodes(json);
makeLinks(json);
//console.log(items);
createKeylines(items);
};
// populates global var itmes with nodes
function makeNodes(param) {
for (i = 0; i < param.data.length ; i++) {
for (j = 0 ; j < 2 ; j++) {
var node = {
type: 'node',
id: param.data[i][j].self,
dt: stringToDateConverter(String(param.data[i][1].data.yyyyMMdd)),
b: 'rgb(100,255,0)',
c: 'rgb(0,0,255)',
t: param.data[i][j].data.name,
subject: param.data[i][j].data.subject
};
items.push(node);
}
}
};
// populates global var itmes with nodes
function makeLinks(json) {
for (i = 0; i < json.data.length; i++) {
var link = {
type: 'link',
id: json.data[i][2].self,
id1: json.data[i][2].start,
id2: json.data[i][2].end,
t: json.data[i][2].metadata.type,
w: 2,
c: 'rgb(0,0,255)'
}
items.push(link);
}
}
Using this technique i was successfully able to plot keylines graph and timebar using only client side Javascript.
but problem Arose when i published this on IIS, it gave me Cross-Domain error which means i had to call Neo4j from server code (C#) and feed it to client side HTML/JS. That's when i found out about Neo4jClient.
Done so Far:
I am Successfully able to read data from Neo4j in my C# code using
public class Person
{
public string name { get; set; }
public int dob { get; set; }
}
var query = client.cypher
.match(("person:Person"))
.return(person => person.As<Person>());
var result = query.results;
foreach (var i in result)
{
Console.WriteLine("people name is:"+i.name);
}
Problem:
Now, i can read only data based on my Cypher Query but can't read other stuff like id, self, start, end URI's and relationships which i need for my UI.
Is there a way to get the Return (*) and all the other meta data using neo4jclient? Any code will help.
Should i stick to Client side ajax call by resolving cross reference errors using JSONP or CORS since it was much easier but i am afraid that it might give me some problems down the line as it is not the proper way to do it?
I can't seem to find any proper documentation for Neo4jClient.
There are so many options i see for return in Intellisense but dont't know how i can use them.
Any help is greatly appreciated.
Thanks in advance.
You can change your return to be:
.Return(person => person.As<Node<Person>>());
Which will return a wrapper around the Data containing all the node goodness, that should get you there.

How to get visitor's location (i.e. country) using geolocation? [closed]

Closed. This question is not about programming or software development. It is not currently accepting answers.
This question does not appear to be about a specific programming problem, a software algorithm, or software tools primarily used by programmers. If you believe the question would be on-topic on another Stack Exchange site, you can leave a comment to explain where the question may be able to be answered.
Closed 4 months ago.
Improve this question
I'm trying to extend the native geolocation function
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
});
}
so that I can use the visitor's country name (perhaps return an informative array).
So far all I've been able to find are functions that display a google maps interface but none actually gave what I want, except for this library which worked well in this example but for some reason didn't work on my computer. I'm not sure why that went wrong there.
Anyways, do you know how I can simply return an array containing information like country, city, etc. from latitude and longitude values?
You can use my service, http://ipinfo.io, for this. It will give you the client IP, hostname, geolocation information (city, region, country, area code, zip code etc) and network owner. Here's a simple example that logs the city and country:
$.get("https://ipinfo.io", function(response) {
console.log(response.city, response.country);
}, "jsonp");
Here's a more detailed JSFiddle example that also prints out the full response information, so you can see all of the available details: http://jsfiddle.net/zK5FN/2/
The location will generally be less accurate than the native geolocation details, but it doesn't require any user permission.
You don't need to locate the user if you only need their country. You can look their IP address up in any IP-to-location service (like maxmind, ipregistry or ip2location). This will be accurate most of the time.
Here is a client-side example with Ipregistry (disclaimer, I am working for):
fetch('https://api.ipregistry.co/?key=tryout')
.then(function (response) {
return response.json();
})
.then(function (payload) {
console.log(payload.location.country.name + ', ' + payload.location.city);
});
If you really need to get their location, you can get their lat/lng with that method, then query Google's or Yahoo's reverse geocoding service.
You can do this natively wihtout relying on IP services. You can get the user's timezone like this:
Intl.DateTimeFormat().resolvedOptions().timeZone
and then extract the country from that value. Here is a working example on CodePen.
You can use your IP address to get your 'country', 'city', 'isp' etc...
Just use one of the web-services that provide you with a simple api like http://ip-api.com which provide you a JSON service at http://ip-api.com/json. Simple send a Ajax (or Xhr) request and then parse the JSON to get whatever data you need.
var requestUrl = "http://ip-api.com/json";
$.ajax({
url: requestUrl,
type: 'GET',
success: function(json)
{
console.log("My country is: " + json.country);
},
error: function(err)
{
console.log("Request failed, error= " + err);
}
});
See ipdata.co a service I built that is fast and has reliable performance thanks to having 10 global endpoints each able to handle >10,000 requests per second!
This answer uses a 'test' API Key that is very limited and only meant for testing a few calls. Signup for your own Free API Key and get up to 1500 requests daily for development.
This snippet will return the details of your current ip. To lookup other ip addresses, simply append the ip to the https://api.ipdata.co?api-key=test url eg.
https://api.ipdata.co/1.1.1.1?api-key=test
The API also provides an is_eu field indicating whether the user is in an EU country.
$.get("https://api.ipdata.co?api-key=test", function (response) {
$("#response").html(JSON.stringify(response, null, 4));
}, "jsonp");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre id="response"></pre>
Here's the fiddle; https://jsfiddle.net/ipdata/6wtf0q4g/922/
I also wrote this detailed analysis of 8 of the best IP Geolocation APIs.
A very easy to use service is provided by ws.geonames.org. Here's an example URL:
http://ws.geonames.org/countryCode?lat=43.7534932&lng=28.5743187&type=JSON
And here's some (jQuery) code which I've added to your code:
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
$.getJSON('http://ws.geonames.org/countryCode', {
lat: position.coords.latitude,
lng: position.coords.longitude,
type: 'JSON'
}, function(result) {
alert('Country: ' + result.countryName + '\n' + 'Code: ' + result.countryCode);
});
});
}​
Try it on jsfiddle.net ...
A free and easy to use service is provided at Webtechriser (click here to read the article) (called wipmania). This one is a JSONP service and requires plain javascript coding with HTML. It can also be used in JQuery. I modified the code a bit to change the output format and this is what I've used and found to be working: (it's the code of my HTML page)
<html>
<body>
<p id="loc"></p>
<script type="text/javascript">
var a = document.getElementById("loc");
function jsonpCallback(data) {
a.innerHTML = "Latitude: " + data.latitude +
"<br/>Longitude: " + data.longitude +
"<br/>Country: " + data.address.country;
}
</script>
<script src="http://api.wipmania.com/jsonp?callback=jsonpCallback"
type="text/javascript"></script>
</body>
</html>
PLEASE NOTE: This service gets the location of the visitor without prompting the visitor to choose whether to share their location, unlike the HTML 5 geolocation API (the code that you've written). Therefore, privacy is compromised. So, you should make judicial use of this service.
I wanted to localize client side pricing for few countries without using any external api, so I used local Date object to fetch the country using
new Date()).toString().split('(')[1].split(" ")[0]
document.write((new Date()).toString().split('(')[1].split(" ")[0])
Basically this small code snippet extracts the first word from Date object. To check for various time zone, you can change the time of your local machine.
In my case, our service only included three countries, so I was able to get the location using the following code.
const countries = ["India", "Australia", "Singapore"]
const countryTimeZoneCodes = {
"IND": 0,
"IST": 0,
"AUS": 1,
"AES": 1,
"ACS": 1,
"AWS": 1,
"SGT": 2,
"SIN": 2,
"SST": 2
} // Probable three characters from timezone part of Date object
let index = 0
try {
const codeToCheck = (new Date()).toString().split('(')[1].split(" ")[0].toUpperCase().substring(0, 3)
index = countryTimeZoneCodes[codeToCheck]
} catch (e) {
document.write(e)
index = 0
}
document.write(countries[index])
This was just to improve user experience. It's not a full proof solution to detect location. As a fallback for not detecting correctly, I added a dropdown in the menubar for selecting the country.
For developers looking for a full-featured geolocation utility, you can have a look at geolocator.js (I'm the author).
Example below will first try HTML5 Geolocation API to obtain the exact coordinates. If fails or rejected, it will fallback to Geo-IP look-up. Once it gets the coordinates, it will reverse-geocode the coordinates into an address.
var options = {
enableHighAccuracy: true,
timeout: 6000,
maximumAge: 0,
desiredAccuracy: 30,
fallbackToIP: true, // if HTML5 geolocation fails or rejected
addressLookup: true, // get detailed address information
timezone: true,
map: "my-map" // this will even create a map for you
};
geolocator.locate(options, function (err, location) {
console.log(err || location);
});
It supports geo-location (via HTML5 or IP lookups), geocoding, address look-ups (reverse geocoding), distance & durations, timezone information and more...
You can simply import in your app.component.ts or whichever component you want to use
import { HttpClient } from '#angular/common/http';
Then make a simple GET request to http://ip-api.com/json
getIPAddress() {
this.http.get("http://ip-api.com/json").subscribe((res: any) => {
console.log('res ', res);
})
}
You will get the following response by using it:
{
"status": "success",
"country": "country fullname here",
"countryCode": "country shortname here",
"region": "region shortname here",
"regionName": "region fullname here",
"city": "city fullname here",
"zip": "zipcode will be in string",
"lat": "latitude here will be in integer",
"lon": "logitude here will be in integer",
"timezone": "timezone here",
"isp": "internet service provider name here",
"org": "internet service provider organization name here",
"as": "internet service provider name with some code here",
"query": "ip address here"
}
You can use ip-api.io to get visitor's location. It supports IPv6.
As a bonus it allows to check whether ip address is a tor node, public proxy or spammer.
JavaScript Code:
function getIPDetails() {
var ipAddress = document.getElementById("txtIP").value;
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
console.log(JSON.parse(xhttp.responseText));
}
};
xhttp.open("GET", "http://ip-api.io/json/" + ipAddress, true);
xhttp.send();
}
<input type="text" id="txtIP" placeholder="Enter the ip address" />
<button onclick="getIPDetails()">Get IP Details</button>
jQuery Code:
$(document).ready(function () {
$('#btnGetIpDetail').click(function () {
if ($('#txtIP').val() == '') {
alert('IP address is reqired');
return false;
}
$.getJSON("http://ip-api.io/json/" + $('#txtIP').val(),
function (result) {
alert('Country Name: ' + result.country_name)
console.log(result);
});
});
});
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<div>
<input type="text" id="txtIP" />
<button id="btnGetIpDetail">Get Location of IP</button>
</div>
If you don't want to use an api and only the country is enough for you, you can use topojson and worldatlas.
import { feature } from "https://cdn.skypack.dev/topojson#3.0.2";
import { geoContains, geoCentroid, geoDistance } from "https://cdn.skypack.dev/d3#7.0.0";
async function success(position) {
const topology = await fetch("https://cdn.jsdelivr.net/npm/world-atlas#2/countries-50m.json").then(response => response.json());
const geojson = feature(topology, topology.objects.countries);
const {
longitude,
latitude,
} = position.coords;
const location = geojson.features
.filter(d => geoContains(d, [longitude, latitude]))
.shift();
if (location) {
document.querySelector('#location').innerHTML = `You are in <u>${location.properties.name}</u>`;
}
if (!location) {
const closestCountry = geojson.features
// You could improve the distance calculation so that you get a more accurate result
.map(d => ({ ...d, distance: geoDistance(geoCentroid(d), [longitude, latitude]) }))
.sort((a, b) => a.distance - b.distance)
.splice(0, 5);
if (closestCountry.length > 0) {
const possibleLocations = closestCountry.map(d => d.properties.name);
const suggestLoctions = `${possibleLocations.slice(0, -1).join(', ')} or ${possibleLocations.slice(-1)}`;
document.querySelector('#location').innerHTML = `It's not clear where you are!<section>Looks like you are in ${suggestLoctions}</section>`;
}
if (closestCountry.length === 0) {
error();
}
}
}
function error() {
document.querySelector('#location').innerHTML = 'Sorry, I could not locate you';
};
navigator.geolocation.getCurrentPosition(success, error);
This code takes longitude and latitude and checks if this point is included in one of the geojson's feature (a spatially bounded entity). I created also a working example.

Categories