
With JEO, creating the interaction between data layers and contextual information is intuitive and interactive. You can post geotagged stories and create richly designed pages for each one of the featured stories. At the same time, by simply imputing the ids of layers hosted on MapBox, you can manage sophisticated maps without losing performance, add legends directly with HTML and set the map parameters. All directly at the WordPress dashboard.
On the Layer settings panel, you can change the layer type.
JEO supports four layer types out-of-the-box:
You can also inform an address, following the standard username/id, to compose your map style. If an access token is needed for this layer, you can put it into the Acess token input.
There’s also an Edit interactions button. Here, you can add popups to your layer when specific actions (clicking or hovering the mouse) are made (e.g.: Clicking on a building and displaying its height)
On the Layer legend panel, you can add legends to your layer (barscale, simple-color, icons or circles) and colorize them.
Creating maps
One of the custom post types that the JEO plugin provides is Map.
Entering the Map post editor, you’ll see a preview of the current map (or a default map, if no layers are selected) and three sidebar panels: Map settings, Map layers, and Related posts.
On the Map settings panel, you can change the initial center of the map setting a latitude and longitude. You can also change the default zoom settings or even disable it.
When visualizing a map in a post, right-clicking and dragging it makes the map rotate. Also, scrolling the mouse wheel changes the map zoom. Both of these options can be enabled or disabled.
On the Map layers panel, you can visualize all the layers which are part of the map.
Clicking on the Edit layers settings, a popup will open. In there, you can add, remove and rearrange layers, define their types and whether their legends will be shown on the map.
A legend can be of one of these types:
On the Related posts panel, you can set which geolocated posts will be used as markers on the post. You can filter the posts by categories, tags, dates interval or, for advanced uses, meta queries using WordPress post_meta.
Geolocating posts
When editing a post, JEO will add an extra panel to the sidebar: Geolocation. Clicking on Geolocate this post, a popup containing two tabs (Map and List) will then be displayed.
New points can be added to the post by clicking on the Add new point button. You can search for a specific location in the search bar.
It’s also possible to choose the relevance of each point, which is useful when you have more than one point.
The Map tab allows you to move a point by dragging its marker and dropping it on the new location.
The List tab allows you to visualize all the created points and delete a specific point.
Map with geolocated posts contain markers on the localization of each post. Clicking on them will open a popup with its title and publication date. Clicking on the title will take you to the post itself.
Map shortcode
A map can be inserted on any page or post using the jeo-map shortcode.
The shortcode accepts three attributes:
Examples:
[jeo-map map_id=99]
You have to inform at least the ID of the Map you want to insert. By default, it will be inserted with a size of 600×600px (or whatever the active theme defines), but you can also change it:
[jeo-map map_id=99 width="800px" height="800px"]
Map block
After creating maps, it is possible to display them apart or inside a block. This functionality makes it possible to group maps, increasing your post organization.
When creating a new post, note that is available a new block category: JEO.
Selecting the JEO Map block, you can search for any map you’ve created.
With a map selected, it is possible to choose an optional alignment (Left, Right, Centre, Wide Width or Full Width). Centre is the standard alignment.
Besides the alignment option, there’s also a group functionality available to arrange maps.
If your map has more than one layer, you can swap them and select which one you want to see, depending on the map layer settings.
One-time map block
It is possible to use maps in posts without having to create a new map or using an existing one. For this, the JEO plugin makes available another type of block: One-time map.
When creating a new post, note that is available a new block category: JEO.
Selecting the JEO One-time Map block, a standard map preview will appear. This type of map allows all the same customizations as a normal JEO Map, such as modifying coordinates, zoom settings, related posts, alignment options and layers settings.
Embedding a map
JEO plugin allows a map to be inserted into a post by pasting a link on the editor. This is what is called Embed map and it’s very easy to be done.
When editing a Map, one of the setting panels is Status & Visibility. There you can find the embed URL of that specific map.
Copying this link and pasting it on the post editor will result in an embedded map.
Warning: If your post displays a Not Found error, do the following steps:
Now you should be able to see the embedded maps with no problems.
Adding new Layer Types
In JEO, maps are rendered using the Mapbox GL JavaScript library. Any new layer type will have to interact with this library to add the layer to the map.
To add a new layer type, there are 2 simple steps:
In short, this is all that is needed to do. In some cases, however, you might need to add extra dependencies to the project. For example, to create a Layer Type to support Carto’s vector layers, we might want to add CartoVL (which is an extension to MapboxGL) to the project.
First, let’s register a new Layer Type by hooking up in the jeo_register_layer_types action:
add_action('jeo_register_layer_types', function($layer_types) { $layer_types->register_layer_type( 'my-layer-type', [ 'script_url' => plugin_dir_url( __FILE__ ) . '/js/layertype.js' ] ); }); register_layer_type method gets 2 parameters. That’s all you need to do on the PHP side. All the magic happens on JavaScript.
Now, let’s create our layertype.js file.
In this file, we are going to register a JavaScript object using the globally available window.JeoLayerTypes.registerLayerType.
The first parameter must be the same slug you defined when you registered your Layer Type on the PHP side, and the second parameter is an object with, at least, three methods.
window.JeoLayerTypes.registerLayerType('tilelayer', { addStyle: function(map, attributes) { // ... }, addLayer: function(map, attributes) { // ... }, getSchema: function(attributes) { // ... } }); Your Layer Type object MUST implement at least these three methods.
params:
returns:
This method will tell JEO which are the options the user has to fill in when creating a new layer of this type.
For example, a raster tile layer type might have only a URL. A Mapbox layer has the Style ID and the optional Access token.
This method must return a Promise with a JSON Schema representation of the layer type options.
This schema must only include layer-type specific information. Every layer, despite its type, has a set of common attributes, such as ID and Name.
For example, the “Tile layer” layer type needs only a URL, so that’s how its getSchema method will look like.
// ... getSchema: function(attributes) { return new Promise( function(resolve, reject) { resolve({ "type": "object", "required": [ "url" ], "properties": { "url": { "type": "string", "title": "URL" } } }); }); }<h3>addStyle(map, attributes)</h3> params:
returns:
In MapboxGL, every map has a Style as a base layer. This method will add the layer as the Map Style, using the setStyle method of the Map object.
This method will be invoked when a layer of this type is added to the map as the base layer.
For example, the “Tile Layer” layer type sets the style as a raster layer:
// ... addStyle: function(map, attributes) { return map.setStyle({ 'version': 8, 'sources': { 'raster-tiles': { 'type': 'raster', 'tiles': [attributes.layer_type_options.url], 'tileSize': 256 } }, 'layers': [{ id: attributes.layer_id, type: 'raster', source: 'raster-tiles' }] }) } Note: The attributes.layer_type_options object holds all the properties declared in the getSchema method. That’s why there is a url there! (See Layer attributes section below)
params:
returns:
This method will add the layer to the map using the addLayer method of the Map object.
This method will be invoked when a layer of this type is added to the map.
For example, the “Tile Layer” layer type adds itself as a raster layer:
// ... addLayer: function(map, attributes) { var layer = { id: attributes.layer_id, source: { type: 'raster', tiles: [attributes.layer_type_options.url], "tileSize": 256 }, type: 'raster' }; if ( ! attributes.visible ) { layer.layout = { visibility: 'none' }; } return map.addLayer(layer); } Note: This method must verify the value of attributes.visible to determine whether this layer should be visible when the map is initialized.
As you saw, each of the above methods gets an argument attributes as input. This argument holds all the information of the layer the user is editing or viewing.
Some attributes are common to any layer type, and others are specific to a layer type. Every layer type-specific attribute a layer has is stored under the layer_type_options attributes.
So these are the keys available in the attributes object:
Geographical Information of a post
Each post can be related to one or more points on the map.
For each point, JEO collects geographical information such as city and country names.
Each related point is stored as one entry of the _related_point metadata key. Each entry is an object with all the information retrieved by the geocoder.
Here is an example of two entries related to the same post, that could be get using:
get_post_meta( $post_id, '_related_point' ); '_related_point' => [ 'relevance' => 'primary', '_geocode_lat' => '-23,54659435', '_geocode_lon' => '-46,644533061712', '_geocode_full_address' => 'Edifício Copan, Rua Araújo, Vila Buarque, República, São Paulo, Região Imediata de São Paulo, Região Metropolitana de São Paulo, Região Intermediária de São Paulo, São Paulo, Região Sudeste, 01046-010, Brasil', '_geocode_country' => 'Brasil', '_geocode_country_code' => '', '_geocode_city' => 'São Paulo', '_geocode_region_level_2' => 'São Paulo', '_geocode_region_level_3' => 'Região Intermediária de São Paulo', '_geocode_city_level_1' => 'Vila Buarque', ], '_related_point' => [ 'relevance' => 'secondary', '_geocode_lat' => '-23,183525102463', '_geocode_lon' => '-46,898231506348', '_geocode_full_address' => 'Rua Jorge Gebran, Parque do Colégio, Chácara Urbana, Jundiaí, Região Imediata de Jundiaí, Região Intermediária de Campinas, São Paulo, Região Sudeste, 13209-090, Brasil', '_geocode_country' => 'Brasil', '_geocode_country_code' => '', '_geocode_city' => 'Jundiaí', '_geocode_region_level_2' => 'São Paulo', '_geocode_region_level_3' => 'Região Intermediária de Campinas', '_geocode_city_level_1' => 'Parque do Colégio', ]<h3>How to search for posts by geoinformation? (indexes)</h3>
When you save geographical information of the points, JEO also creates other metadata that will allow developers to query posts by specific geographical information.
Since each point is stored as serialized data in the database, this would not allow us to filter posts by country_code for example. That’s why we create indexes.
For the example above, this post would also have one individual metadata entry for each information, like this:
[ '_geocode_lat_p' => '-23,54659435', '_geocode_lon_p' => '-46,644533061712', '_geocode_country_p' => 'Brasil', '_geocode_country_code_p' => '', '_geocode_city_p' => 'São Paulo', '_geocode_region_level_2_p' => 'São Paulo', '_geocode_region_level_3_p' => 'Região Intermediária de São Paulo', '_geocode_city_level_1_p' => 'Vila Buarque', '_geocode_lat_s' => '-23,183525102463', '_geocode_lon_s' => '-46,898231506348', '_geocode_country_s' => 'Brasil', '_geocode_country_code_s' => '', '_geocode_city_s' => 'Jundiaí', '_geocode_region_level_2_s' => 'São Paulo', '_geocode_region_level_3_s' => 'Região Intermediária de Campinas', '_geocode_city_level_1_s' => 'Parque do Colégio', ]
Note: _s and _p suffixes indicate if the relevance of that information is primary or secondary.
Note 2: Full addresses are not indexed
Now we have all the information as individual metadata and this allows me to query them, however, the pairs are disconnected (if I had more than one primary point, it would be impossible to know what are the latitude-longitude pairs. That’s why the information we actually use is the serialized object).
Give me all the posts that have primary points with the country code 'BR':
$posts = new WP_Query([ 'meta_query' => [ [ 'key' => '_geocode_country_code_p', 'value' => 'BR' ] ] ]);
Give me all the posts whose city is 'Manaus':
$posts = new WP_Query([ 'meta_query' => [ [ 'key' => '_geocode_city_s', 'value' => 'Manaus' ], [ 'key' => '_geocode_city_p', 'value' => 'Manaus' ], 'relation' => 'OR' ] ]);
Writing a Geocoder
A Geocoder is a service that finds geographical coordinates from a search by address information. It’s also able to get address details based on the geographical coordinates, which is called Reverse Geocoding.
JEO needs a geocoder service in a few situations, such as when users indicate to where on a map a story (posts) is related.
JEO comes with two native geocoder services users can choose from: Nominatim and Google. But new services can easily be added by plugins. This page documents how to do this.
Hook a function to the jeo_register_geocoders action and call the register with the following code:
add_action('jeo_register_geocoders', function($geocoders) { $geocoders->register_geocoder([ 'slug' => 'my-geocoder', 'name' => 'My Geocoder', 'description' => __('My Geocoder description', 'my-textdomain'), 'class_name' => 'MyGeocoderClass' ]); }); This will tell JEO that there is a new Geocoder service available and give some information about it.
Now we need to create the geocoder class. This will be a class that extends \Jeo\Geocoder and implement some methods that do the actual geocoding.
Inside the same hook, declare the class and two required methods:
While geocode() returns an array of search results, reverse_geocode() returns only one result.
Each result is an array that must have only the keys expected by the JEO plugin, so each Geocoder must find the best correspondence between each field and the fields expected by JEO.
Note: Only lat and lon are required.
Sample response with all accepted fields:
[ [ 'lat' => '', 'lon' => '', 'full_address' => '', 'country' => '', 'country_code' => '', 'region_level_1' => '', 'region_level_2' => '', // State goes here 'region_level_3' => '', 'city' => '', 'city_level_1' => '', ] ]
Here is a simple example:
add_action('jeo_register_geocoders', function($geocoders) { $geocoders->register_geocoder([ 'slug' => 'my-geocoder', 'name' => 'My Geocoder', 'description' => __('My Geocoder description', 'my-textdomain'), 'class_name' => 'MyGeocoderClass' ]); class MyGeocoderClass extends \Jeo\Geocoder { public function geocode($search_string) { $params = [ 'q' => $search_string, 'format' => 'json', 'addressdetails' => 1 ]; $r = wp_remote_get( add_query_arg($params, 'https://my-geocoder-server.org/search') ); $data = wp_remote_retrieve_body( $r ); $data = \json_decode($data); $response = []; if (\is_array($data)) { foreach ($data as $match) { $r = $this->format_response_item( (array) $match ); if ($r) $response[] = $r; } } return $response; } public function reverse_geocode($lat, $lon) { $params = [ 'lat' => $lat, 'lon' => $lon, 'format' => 'json', 'addressdetails' => 1 ]; $r = wp_remote_get( add_query_arg($params, 'https://my-geocoder-server.org/reverse') ); $data = wp_remote_retrieve_body( $r ); $data = \json_decode($data); return $this->format_response_item( (array) $data ); } private function format_response_item($match) { $response = [ 'lat' => $match['lat'], 'lon' => $match['lon'], 'full_address' => $match['display_name'], 'country' => $match['country'], 'country_code' => $match['country_code'], 'region_level_1' => $match['region_level_1'], 'region_level_2' => $match['region_level_2'], // State goes here 'region_level_3' => $match['region_level_3'], 'city' => $match['city'], 'city_level_1' => $match['city_level_1'], ]; return $response; } } }); And that’s it! Your new Geocoder is ready!
Some geocoder services might need or offer additional settings. Some might require the user to enter its API key, others might let the users restrict the search to a specific country to get better results when searching.
You can also easily add new settings to your Geocoder that will automatically be presented to the user on the Settings page.
Declare a method get_settings() in your class that will return an array of all the settings your Geocoder accepts.
Each setting is described by an array with the following keys:
Let’s see an example only with the relevant code:
add_action('jeo_register_geocoders', function($geocoders) { // ... class MyGeocoderClass extends \Jeo\Geocoder { // ... public function get_settings() { // Note it is an array of arrays return [ [ 'slug' => 'api_key', 'name' => __('API Key', 'my-text-domain'), 'description' => __('Enter the API key you can get visiting your panel at my-gecoder.org/panel', 'my-text-domain') ] ]; } } }); And this is what you will see in the admin panel:
Now that you have registered a setting and the user can change its value in the admin panel, you can use it in your geocoder.
To get its value, simply call $this->get_option($option_name).
Example:
// ... // ... public function geocode($search_string) { $params = [ 'q' => $search_string, 'format' => 'json', 'addressdetails' => 1, 'api_key' => $this->get_option('api_key') ]; // ... return $response; } // ...<h3>Declaring default values</h3> You can also add the get_default_options() method to your class to set default values for each setting. This is optional and is done like this:
add_action('jeo_register_geocoders', function($geocoders) { // ... class MyGeocoderClass extends \Jeo\Geocoder { // ... public function get_default_options() { return [ 'api_key' => 'sand-box-api-key' // the key must match the slug of the setting registered in get_settings() ]; } } });<h3>Advanced: Even further settings customization</h3> If your geocoder needs some special settings that a simple text input won’t handle, there is yet another method you can declare to add arbitrary HTML code to the Settings page.
settings_footer($settings) must echo the HTML code that will be rendered at the end of your Geocoder settings page.
It received the $settings object, which is an instance of \Jeo\Settings and has some helpers you can use.
You only need to print form fields with the right names and JEO will take care of saving them for you.
To get the right field name use $settings->get_geocoder_option_field_name($name).
Example:
// ... // ... public function settings_footer($settings) { ?> <p><strong>My Select option</strong></p> <select name="<?php echo $settings->get_geocoder_option_field_name('new_option'); ?>"> <option value="yes" <?php selected( $this->get_option('new_option'), 'yes' ); ?> > Yes </select> <option value="no" <?php selected( $this->get_option('new_option'), 'no' ); ?> > No </select> </select> <?php } // ... Note: selected() is a native WordPress function. See the official documentation
Migration
Notes on changes in DB structure from old JEO that will need to have migrations written.
On old JEO, some meta_keys are prefixed by an underscore (_) and others aren’t:
Let’s have them all with an underscore at the beginning.
Starting from $0 per month.
Rating
Reviewers
No reviews
Tags
Developed By
earthjournalism
Quick & Easy
Common Ninja has a large selection of powerful Wordpress plugins that are easy to use, fully customizable, mobile-friendly and rich with features — so be sure to check them out!
Testimonial plugins for Wordpress
Galleries plugins for Wordpress
SEO plugins for Wordpress
Contact Form plugins for Wordpress
Forms plugins for Wordpress
Social Feeds plugins for Wordpress
Social Sharing plugins for Wordpress
Events Calendar plugins for Wordpress
Sliders plugins for Wordpress
Analytics plugins for Wordpress
Reviews plugins for Wordpress
Comments plugins for Wordpress
Portfolio plugins for Wordpress
Maps plugins for Wordpress
Security plugins for Wordpress
Translation plugins for Wordpress
Ads plugins for Wordpress
Video Player plugins for Wordpress
Music Player plugins for Wordpress
Backup plugins for Wordpress
Privacy plugins for Wordpress
Optimize plugins for Wordpress
Chat plugins for Wordpress
Countdown plugins for Wordpress
Email Marketing plugins for Wordpress
Tabs plugins for Wordpress
Membership plugins for Wordpress
popup plugins for Wordpress
SiteMap plugins for Wordpress
Payment plugins for Wordpress
Coming Soon plugins for Wordpress
Ecommerce plugins for Wordpress
Customer Support plugins for Wordpress
Inventory plugins for Wordpress
Video Player plugins for Wordpress
Testimonials plugins for Wordpress
Tabs plugins for Wordpress
Social Sharing plugins for Wordpress
Social Feeds plugins for Wordpress
Slider plugins for Wordpress
Reviews plugins for Wordpress
Portfolio plugins for Wordpress
Membership plugins for Wordpress
Forms plugins for Wordpress
Events Calendar plugins for Wordpress
Contact plugins for Wordpress
Comments plugins for Wordpress
Analytics plugins for Wordpress
Common Ninja Apps
Browse our extensive collection of compatible plugins, and easily embed them on any website, blog, online store, e-commerce platform, or site builder.
Use a pricing slider to show dynamic prices by quantity, help visitors compare options, and support confident purchases.
Showcase visuals with an image slider that displays multiple images in a smooth slideshow, improves design, and keeps visitors engaged.
Create interactive diagrams with a diagrams widget that lets you build and customize flow charts, improve clarity, and help visitors understand complex ideas easily.
Create engaging stop motion displays with a stop motion player that turns image sequences into interactive animations to boost creativity and visitor engagement.

Add a Google powered search bar that delivers relevant results, improves navigation, and helps visitors find content fast.
Show LinkedIn posts in a live feed that keeps updates current, builds credibility, and helps visitors engage with your brand.
Add opening hours to your site to give visitors clear, reliable business information that improves trust, reduces confusion, and supports user experience.
Use an image magnifier to let visitors zoom in on photos, view fine details clearly, and enjoy a more accessible and informative visual experience.
Showcase your abilities with a structured skill list that highlights strengths clearly, builds credibility, and improves your chances of getting hired.
Present images in a Slideshow carousel that rotates or slides through visuals, helping you highlight key content within a clean, engaging layout.
Use image hover effects to add animations, highlight key visuals, and keep visitors engaged with dynamic image reveals.
Use a coupon popup to highlight special offers, capture email leads, reduce cart abandonment, and turn more visitors into paying customers.
More plugins
The Common Ninja Search Engine platform helps website builders find the best site widgets, apps, plugins, tools, add-ons, and extensions! Compatible with all major website building platforms - big or small - and updated regularly, our Search Engine tool provides you with the business tools your site needs!
