17 Posts
L.A.Willette
7 years ago
Topic
Hi everybody,
I’m trying to do a custom validation in my form that consists in checking if the article coordinates (latitude and longitude, calculated on submit at first stage of the form with “Address to Coordinates”) are within a certain radius of the user coordinates (user’s latitude and longitude are already stored at registration of the user). If the article geolocation is not inside the radius of the user geolocation (let’s say within 100km radius), then a message error comes out.
I’m trying to do it with “Function” which provides validation on a field based on a jQuery function… but unfortunately I don’t have a lot of programming skills. And don’t even know if it’s really possible using this “Function” method.
Searching the web for a solution I found out that I should use the Haversine formula in order to calculate the distance between 2 coordinates. Something like this:
function distance(lat1,lon1,lat2,lon2){
  var R = 6371; // Earth's radius in Km
  return Math.acos(Math.sin(lat1)*Math.sin(lat2) + 
                  Math.cos(lat1)*Math.cos(lat2) *
                  Math.cos(lon2-lon1)) * R;
}


if (distance(user.lat, user.lon, post.lat, post.lon) <= desiredRadiusInKm){
  // return true;
} else {
  // return false;
}<br>
Another solution found is using Google Maps API:
function arePointsNear(point1, point2) {
    var sw = new google.maps.LatLng(point2.lat() - 0.005, point2.lng() - 0.005);
    var ne = new google.maps.LatLng(point2.lat() + 0.005, point2.lng() + 0.005);
    var bounds = new google.maps.LatLngBounds(sw, ne);
    if (bounds.contains (point1))
    	return true;


    return false;
}
Do you think one of these codes could work for me? If yes, should I put it in my form custom template? And how should I call my fields values in the function calculations? Thanks a lot for your attention.

Luca
Get a VIP membership
449 Posts
gebeer
7 years ago
1
Level 1
Hello,

I'm afraid parts of your question go a little beyond the scope of this forum.

I would go with the second solution that uses the Gmaps API.

But I don't think you can do it within the 'Function' field for validation.

You can do this, though, with the Code Pack.

With that plugin you can write your own jQuery/Javascript code that gets executed wen the page loads. In your custom JS code you can access all available fields and their values on that page, even hidden ones.

So you could setup hidden fields that hold the user coordinates and use them for your calculations.

But as the address to coordinates plugin calculates lat and lng only on save you will have the values lat and lng only available after save. And that might be a problem. You could use a custom Seblod template or a position override where you place all your JS/jQuery logic that calculates lat lng on the fly and does the calculations for distance. That way you wouldn't even need the Code Pack.

More info about templating and position overrides here.

Another benefit of using position overrides is that you can learn a lot about PHP along the way :-)

For writing the necessary JS/jQuery code, read up on those topics and try. There's tons of tutorials out there and also a lot about GMaps API with good examples.

Good luck!
17 Posts
L.A.Willette
7 years ago
0
Level 2
Hi gebeer,
thank you so much for your suggestions! I think I’ll try first with position override of my site form. I apologize if my questions were off topic…

Since address to coordinates stores lat and long values only after saving I'm also trying to use Stages in my form. In the first stage user has to compile only the fields needed for address to coordinates calculations. After clicking Next button and going to second stage of the form (where user compiles all remaining fields) I looked at the db (before compiling fields of second stage and saving the article) and noticed that address to coordinates already calculated and stored lat and long values for the new article. I hope in this way to have the coordinates values available for distance calculations (at least at second stage of the form).
I'll keep you updated.
Thanks again!
449 Posts
gebeer
7 years ago
3
Level 1
Hi L.A.Willette,

I think it is a good way to go with a position override.

You don't really need the plugin address to coordinates to do the geocoding. Once you have a position override, you can do this on the fly without stages in your form.

I wrote a step by step howto here in the forum on getting coordinates from an address on the fly in a position override.

You can find some useful code there. Of course, you will have to add your code.

Hope this helps
gerhard
17 Posts
L.A.Willette
7 years ago
2
Level 2
Hi Gerhard,
yes it was really helpful! Thanks to your post I managed to do what I needed and I love the draggable marker for fine tuning!
I mixed your code with this one that checks if coords are inside a circle through GoogleMaps API. For country and region fields I’m using Dynamic Cascade plugin with imported Virtuemart country/region tables (#__countries, #__states) in database.

This is the code now I’m using in my mainbody.php override.
<?php
// No Direct Access
defined( '_JEXEC' ) or die;
$document = JFactory::getDocument();
$document->addScript( 'http://maps.googleapis.com/maps/api/js?sensor=false' ); // Google Maps JS
$buttonText = 'Verify Geodata'; // Button text
?>
<script type="text/javascript">
/* Display a Google Map and pinpoint a location */
(function($) {
$(document).ready(function() { 
    var map = $('#map_canvas'); // Map canvas
    var lat = $('#art_map_latitude'); // Post latitude
    var lng = $('#art_map_longitude'); // Post longitude
    var defaultLat = $('#art_user_latitude :selected').text(); // User latitude
    var defaultLng = $('#art_user_longitude :selected').text(); // User longitude
    var street = $('#art_map_street'); // Address
    var postcode = $('#art_map_zip'); // Postcode
    var city = $('#art_map_city'); // City
    var countryList = $('#art_map_country').change(function() { // Country list
    	var countryVal = $('option:selected', this).text(); 
    	if (countryVal == '- Select an Option') { $('#art_map_country_select').val(' ');}
    	else { $('#art_map_country_select').val(countryVal);} }).change();
    var country = $('#art_map_country_select'); // Country
    var regionList = $('#art_map_region').change(function() { // Region list
    	var regionVal = $('option:selected', this).text();
    	if (regionVal == '- Select an Option -') { $('#art_map_region_select').val(' ');}
    	else { $('#art_map_region_select').val(regionVal);} }).change();
    var region = $('#art_map_region_select'); // Region
    var notes = $('#notes'); // Messages
    var zoom = 8; // Zoom level
    var	infoWindow = '<span class="icon-map-marker"> Your post location</span>'; // Marker info window
    var messageTrue = '<span class="icon-map-marker" style="color:green"> Geolocalization successful!</span>'; // Success message
    var messageFalse = '<span class="icon-map-marker" style="color:red"> Geolocalization failed. Please select another location.</span>'; // Failure message
    // Start processing data
    latVal = lat.val();
    lngVal = lng.val();
    if (latVal == '') {
        latVal = defaultLat;
        lngVal = defaultLng;
    }
    initmap('map_canvas', lat, lng, zoom, 'ROADMAP');
    function initmap(mapId, lat, lng, zoomFactor, mapType) {
    var options = {
        zoom: zoomFactor,
        draggable: true, 
        center: null,
        mapTypeId: google.maps.MapTypeId.HYBRID,
        scrollwheel: true,    
        mapTypeControlOptions: {
            style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
        },
        scaleControl: true
    };    
        if(mapType == 'SATELLITE') options.mapTypeId = google.maps.MapTypeId.SATELLITE; 
        else if(mapType == 'ROADMAP') options.mapTypeId = google.maps.MapTypeId.ROADMAP; 
        options.center = new google.maps.LatLng(latVal, lngVal);
        var map = new google.maps.Map(document.getElementById(mapId), options);
        var userCoords = new google.maps.LatLng(defaultLat, defaultLng),
        	marker = new google.maps.Marker({
            position: options.center, 
            map: map,
            draggable: options.draggable    
        }),
            userMarker = new google.maps.Marker({
            position: userCoords,
            title: 'Location',
            map: map,
            visible: false,
            draggable: false
        }),
        	postInfo = new google.maps.InfoWindow({
            content: infoWindow
        }),
        	circleRadius = 25000, // Unit: meters
        	circle = new google.maps.Circle({
            map: map,
            clickable: false,
            radius: circleRadius,
            fillColor: '#6cf',
            fillOpacity: .4,
            strokeColor: '#069',
            strokeOpacity: .4,
            strokeWeight: .8
        }); 
        	circle.bindTo('center', userMarker, 'position'); // Attach circle to user position
        	var bounds = circle.getBounds(); // Bounds of the circle
            google.maps.event.addListener(marker, 'dragend', function() {
            postCoords = new google.maps.LatLng(marker.position.lat(), marker.position.lng());
    	});
    	    google.maps.event.addListener(marker, 'click', function() {
			postInfo.open(map, marker);
		});
        	google.maps.event.addListener(marker, 'dragend', function(event) { // Recalculate coordinates on marker dragging 
            var geocoder = new google.maps.Geocoder();  
            var position = this.getPosition();    
            lat.val(position.lat());
            lng.val(position.lng());            
            geocoder.geocode({ 'latLng': position }, function() {
                if(bounds.contains(position)) {
                    notes.html(messageTrue);
                    notes.show();
                    lat.css({'color': 'green'});
                    lng.css({'color': 'green'});
                    }
				else {
                    notes.html(messageFalse);
                    notes.show();
					lat.val('Not allowed!');
                    lng.val('Not allowed!');
					lat.css({'color': 'red'});
                    lng.css({'color': 'red'});
                    };
                });
        });
        $('#calculate').click(function() {
            postAddress = street.val() + ' ' + postcode.val() + ' ' + city.val() + ' ' + region.val() + ' ' + country.val(); // Generate address string from input fields
            var geocoder = new google.maps.Geocoder();
            geocoder.geocode({ 'address': postAddress}, function(results, status) {
                if(status == google.maps.GeocoderStatus.OK && results[0]) {
                    var position = results[0].geometry.location;
                    map.setCenter(position);
                    marker.setPosition(position);
                    lat.val(position.lat());
                    lng.val(position.lng());
                }
                if(bounds.contains(position)) {
                	notes.html(messageTrue);
                	notes.show();
                	lat.css({'color': 'green'});
                	lng.css({'color': 'green'});
                }
                else {
                	notes.html(messageFalse);
                	notes.show();
					lat.val('Not allowed!');
                	lng.val('Not allowed!');
					lat.css({'color': 'red'});
                	lng.css({'color': 'red'});
                };
            });
            return true;
        }); 
    };
});
})(jQuery);
</script>
<div><?php echo $cck->renderField('art_title');?></div>
<div><?php echo $cck->renderField('art_type');?></div>
<div><?php echo $cck->renderField('art_map_country');?></div>
<div><?php echo $cck->renderField('art_map_region');?></div>
<div><?php echo $cck->renderField('art_map_city');?></div>
<div><?php echo $cck->renderField('art_map_street');?></div>
<div class="largepaddingbottom"><?php echo $cck->renderField('art_map_zip');?></div>
<div><a id="calculate" class="btn btn-success btn-medium"><?php echo $buttonText; ?></a> <span id="notes" style="display:none;"></span></div>
<div><?php echo $cck->renderField('art_introtext');?></div>
<div><?php echo $cck->renderField('art_tags');?></div>
<div><?php echo $cck->renderField('art_image_fulltext');?></div>
<div><?php echo $cck->renderField('art_image_gallery');?></div>
<div><?php echo $cck->renderField('button_submit');?></div>

This is a screenshot when calculated position is inside the circle:


This is when calculated position is not inside the circle:

In this way I was able to do the validation on lat/lng fields with Seblod built-in alphanumeric plugin: if coords are inside the circle then lat/lng fields are filled with coords (only numbers) calculated with Gmaps API; if coords are not inside the circle then lat/lng fields are filled via JS with the value “Not allowed!” (with the special character ‘!’ at the end); Lat and Lng fields are visible under the map in screenshots and both set read-only.
Now I’m wondering if the validation made in this way can be skipped somehow.
449 Posts
gebeer
7 years ago
0
Level 3
Hi L.A.Willette,

this looks great!

Thanks for sharing your code here. I'm sure it can be useful for others.

Cheers
Gerhard

PS: Please mark the topic as solved
17 Posts
L.A.Willette
7 years ago
0
Level 3
Alphanumeric validation wasn’t really working indeed. I just found out that the validation on lat/lng fields can be done, however, with Regex plugin:

  • Regex for Latitude field: matches numbers between -90 ~ +90 followed by a dot and up to 100 decimals.
/^-?([1-8]?[1-9]|[1-9]0)\.{1}\d{1,100}$/

  • Regex for Longitude field: matches numbers between -180 ~ +180 followed by a dot and up to 100 decimals.
/^-?([1]?[1-7][1-9]|[1]?[1-8][0]|[1-9]?[0-9])\.{1}\d{1,100}$/
449 Posts
gebeer
7 years ago
0
Level 1
Thanks again for sharing :-)
Get a VIP membership