Adding an option to hide map in browse events.

Open Event provides filtering while browsing events. These filters are present in a sidebar which also consists of a map. In this blog, I will describe how I implemented the feature to toggle the visibility of the map present in sidebar for mobile devices.

About the issue

This issue was part of improvements decided for the browse events section. Earlier the map in sidebar was shown irrespective of the user’s device. It is essentially not required to always show the map in mobile device and is a better choice to provide user with an option to view or hide the map.

Sidebar as viewed from an android device before the fix was merged.

The Solution

A button is introduced in the mobile view which controls if the map should be visible or hidden. In the sidebar component (app/components/explore/side-bar.js), a variable “isMapVisible” which decides if the map should be visible (if it is true) or not (if it is false) at a particular instant. A new action “toggleMap” is written which changes the value of “isMapVisible” whenever the button is clicked.

isMapVisible = true;
@action
  toggleMap() {
    this.toggleProperty(‘isMapVisible’);
  }

In the handlebar file (app/templates/components/explore/side-bar.hbs), the map and associated text is changed as per the truthy or falsy value of the “isMapVisible” in the component.

<div class=”map item {{if (not isMapVisible) ‘mobile hidden’}}”>

{{#if device.isMobile}}
  <div class=”ui bottom attached button” role=”button” {{action ‘toggleMap’}}> {{if (not isMapVisible) ‘Show’ ‘Hide’}} Map </div>
{{/if}}

After making the changes, the sidebar looks as follows on the mobile devices

The above images are from an Android device.

Resources:

Issue: https://github.com/fossasia/open-event-frontend/issues/3122
Pull Request: https://github.com/fossasia/open-event-frontend/pull/3444

Ember Docs:https://guides.emberjs.com/v2.14.0/tutorial/simple-component/Toggle Component Tutorial: https://www.learnhowtoprogram.com/ember-js/ember-js/components-hide-show-image

Continue ReadingAdding an option to hide map in browse events.

Implementing Map View in Devices Tab in Settings

The Table View implemented in the Devices tab in settings on SUSI.AI Web Client has a column “geolocation” which displays the latitudinal and longitudinal coordinates of the device. These coordinates needed to be displayed on a map. Hence, we needed a Map View apart from the Table View, dedicated to displaying the devices pinpointed on a map. This blog post explains how this feature has been implemented on the SUSI.AI Web Client.

Modifying the fetched data of devices to suitable format

We already have the fetched data of devices which is being used for the Table View. We need to extract the geolocation data and store it in a different suitable format to be able to use it for the Map View. The required format is as follows:

[
   {
      "location":{
         "lat": latitude1,
         "lng": longitude2
      }
   },
   {
      "location":{
         "lat": latitude1,
         "lng": longitude2
      }
   }
]

 

To modify the fetched data of devices to this format, we modify the apiCall() function to facilitate extraction of the geolocation info of each device and store them in an object, namely ‘mapObj’. Also, we needed variables to store the latitude and longitude to use as the center for the map. ‘centerLat’ and ‘centerLng’ variables store the average of all the latitudes and longitudes respectively. The following code was added to the apiCall() function to facilitate all the above requirements:

let mapObj = [];
let locationData = {
  lat: parseFloat(response.devices[i].geolocation.latitude),
  lng: parseFloat(response.devices[i].geolocation.longitude),
};
centerLat += parseFloat(response.devices[i].geolocation.latitude);
centerLng += parseFloat(response.devices[i].geolocation.longitude);
let location = {
  location: locationData,
};
mapObj.push(location);
centerLat = centerLat / mapObj.length;
centerLng = centerLng / mapObj.length;
if (mapObj.length) {
  this.setState({
    mapObj: mapObj,
    centerLat: centerLat,
    centerLng: centerLng,
  });
}

 

The following code was added in the return function of Settings.react.js file to use the Map component. All the modified data is passed as props to this component.

<MapContainer
  google={this.props.google}
  mapData={this.state.mapObj}
  centerLat={this.state.centerLat}
  centerLng={this.state.centerLng}
  devicenames={this.state.devicenames}
  rooms={this.state.rooms}
  macids={this.state.macids}
/>

 

The implementation of the MapContainer component is as follows:

 componentDidUpdate() {
    this.loadMap();
  }

  loadMap() {
    if (this.props && this.props.google) {
      const {google} = this.props;
      const maps = google.maps;
      const mapRef = this.refs.map;
      const node = ReactDOM.findDOMNode(mapRef);

      const mapConfig = Object.assign({}, 
        
          center: { lat: this.props.centerLat, lng: this.props.centerLng },
          zoom: 2,
        }
      )
      this.map = new maps.Map(node, mapConfig);
    }
  }

 

Let us go over the code of MapContainer component step by step.

  1. Firstly, the componentDidUpdate() function calls the loadMap function to load the google map.

  componentDidUpdate() {
    this.loadMap();
  }

 

  1. In the loadMap() function, we first check whether props have been passed to the MapContainer component. This is done by enclosing all contents of loadMap function inside an if statement as follows:

  if (this.props && this.props.google) {
    // All code of loadMap() function
  } 

 

  1. Then we set the prop value to google, and maps to google maps props. This is done as follows:

  const {google} = this.props;
  const maps = google.maps;

 

  1. Then we look for HTML div ref ‘map’ in the React DOM and name it ‘node’. This is done as follows:

  const mapRef = this.refs.map;
  const node = ReactDOM.findDOMNode(mapRef);

 

  1. Then we set the center and the default zoom level of the map using the props we provided to the MapContainer component.

  {
    center: { lat: this.props.centerLat, lng: this.props.centerLng },
    zoom: 2,
  }

 

  1. Then we create a new Google map on the specified node (ref=’map’) with the specified configuration set above. This is done as follows:

this.map = new maps.Map(node, mapConfig);

 

  1. In the render function of the MapContainer component, we return a div with a ref ‘map’ as follows:

 render() {
    return (
      <div ref="map" style={style}>
        loading map...
      </div>
    );
  }

 

This is how the Map View has been implemented in the Devices tab in Settings on SUSI.AI Web Client.

Resources

Continue ReadingImplementing Map View in Devices Tab in Settings

Displaying Top Hashtags by sorting Hashtags based on the frequency

It is a good idea to display top hashtags on sidebar of loklak. To represent them, it is really important to sort out all unique hashtags on basis of frequency from results obtained from api.loklak. The implementation of the process involved would be discussed in this blog.

Raw Hashtag result

The Hashtags obtained as a parameter of type array containing array of strings into the sortHashtags() method inside the component typescript file of Info-box Component is in Raw Hashtag form.

Making Array of all Hashtags

Firstly, all the Hashtags would be added to a new Array – stored.

sortHashtags( statistics ) {
    let stored = [];
    if (statistics !== undefined && 
        statistics.length !== 0) {
        for (const s in statistics) {
            if (s) {
                for (let i = 0;i <
                    statistics[s].length; i++) {
                    stored.push(statistics[s][i]);
                }
            }
        }
    }
}

 

stored.push( element ) will add each element ( Hashtag ) into the stored array.

Finding frequency of each unique Hashtag

array.reduce() method would be used to store all the Hashtags inside the stored array with frequency of each unique Hashtag (e.g. [ ‘Hashtag1’: 3, ‘Hashtag2’: 2, ‘Hashtag3’: 5, … ]), where Hashtag1 has appeared 3 times, Hashtag2 appeared 2 times and so on.

stored = stored.reduce(function (acc, curr) {
    if (typeof acc[curr] === undefined’) {
        acc[curr] = 1;
    } else {
        acc[curr] += 1;
    }
    return acc;
}, []);

 

stored.reduce() would store the result inside stored array in the format mentioned above.

Using Object to get the required result

Object would be used with different combination of associated methods such as map, filter, sort and slice to get the required Top 10 Hashtags sorted on the basis of frequency of each unique Hashtag.

this.topHashtags = Object.keys(stored)
    .map(key => key.trim())
    .filter(key => key !== ”)
    .map(key => ([key, stored[key]]))
    .sort((a, b) => b[1]  a[1])
    .slice(0, 10);

 

At last, the result is stored inside topHashtags array. First map method is used to trim out spaces from all the keys ( Hashtags ), first filter is applied to remove all those Hashtags which are empty and then mapping each Hashtag as an array with a unique index inside the containing array. At last, sorting each Hashtag on basis of the frequency using sort method and slicing the results to get Top 10 Hashtags to be displayed on sidebar of loklak.

Testing Top Hashtags

Search something on loklak.org to obtain sidebar with results. Now look through the Top 10 Hashtags being displayed on the Sidebar info-box.

Resources

Continue ReadingDisplaying Top Hashtags by sorting Hashtags based on the frequency

Generating Map Action Responses in SUSI AI

SUSI AI responds to location related user queries with a Map action response. The different types of responses are referred to as actions which tell the client how to render the answer. One such action type is the Map action type. The map action contains latitude, longitude and zoom values telling the client to correspondingly render a map with the given location.

Let us visit SUSI Web Chat and try it out.

Query: Where is London

Response: (API Response)

The API Response actions contain text describing the specified location, an anchor with text ‘Here is a map` linked to openstreetmaps and a map with the location coordinates.

Let us look at how this is implemented on server.

For location related queries, the key where is used as an identifier. Once the query is matched with this key, a regular expression `where is (?:(?:a )*)(.*)` is used to parse the location name.

"keys"   : ["where"],
"phrases": [
  {"type":"regex", "expression":"where is (?:(?:a )*)(.*)"},
]

The parsed location name is stored in $1$ and is used to make API calls to fetch information about the place and its location. Console process is used to fetch required data from an API.

"process": [
  {
    "type":"console",
    "expression":"SELECT location[0] AS lon, location[1] AS lat FROM locations WHERE query='$1$';"},
  {
    "type":"console",
    "expression":"SELECT object AS locationInfo FROM location-info WHERE query='$1$';"}
],

Here, we need to make two API calls :

  • For getting information about the place
  • For getting the location coordinates

First let us look at how a Console Process works. In a console process we provide the URL needed to fetch data from, the query parameter needed to be passed to the URL and the path to look for the answer in the API response.

  • url = <url>   – the url to the remote json service which will be used to retrieve information. It must contain a $query$ string.
  • test = <parameter> – the parameter that will replace the $query$ string inside the given url. It is required to test the service.

For getting the information about the place, we used Wikipedia API. We name this console process as location-info and added the required attributes to run it and fetch data from the API.

"location-info": {
  "example":"http://127.0.0.1:4000/susi/console.json?q=%22SELECT%20*%20FROM%20location-info%20WHERE%20query=%27london%27;%22",
  "url":"https://en.wikipedia.org/w/api.php?action=opensearch&limit=1&format=json&search=",
  "test":"london",
  "parser":"json",
  "path":"$.[2]",
  "license":"Copyright by Wikipedia, https://wikimediafoundation.org/wiki/Terms_of_Use/en"
}

The attributes used are :

  • url : The Media WIKI API endpoint
  • test : The Location name which will be appended to the url before making the API call.
  • parser : Specifies the response type for parsing the answer
  • path : Points to the location in the response where the required answer is present

The API endpoint called is of the following format :

https://en.wikipedia.org/w/api.php?action=opensearch&limit=1&format=json&search=LOCATION_NAME

For the query where is london, the API call made returns

[
  "london",
  ["London"],
  ["London  is the capital and most populous city of England and the United Kingdom."],
  ["https://en.wikipedia.org/wiki/London"]
]

The path $.[2] points to the third element of the array i.e “London  is the capital and most populous city of England and the United Kingdom.” which is stored in $locationInfo$.

Similarly to get the location coordinates, another API call is made to loklak API.

"locations": {
  "example":"http://127.0.0.1:4000/susi/console.json?q=%22SELECT%20*%20FROM%20locations%20WHERE%20query=%27rome%27;%22",
  "url":"http://api.loklak.org/api/console.json?q=SELECT%20*%20FROM%20locations%20WHERE%20location='$query$';",
  "test":"rome",
  "parser":"json",
  "path":"$.data",
  "license":"Copyright by GeoNames"
},

The location coordinates are found in $.data.location in the API response. The location coordinates are stored as latitude and longitude in $lat$ and $lon$ respectively.

Finally we have description about the location and its coordinates, so we create the actions to be put in the server response.

The first action is of type answer and the text to be displayed is given by $locationInfo$ where the data from wikipedia API response is stored.

{
  "type":"answer",
  "select":"random",
  "phrases":["$locationInfo$"]
},

The second action is of type anchor. The text to be displayed is `Here is a map` and it must be hyperlinked to openstreetmaps with the obtained $lat$ and $lon$.

{
  "type":"anchor",
  "link":"https://www.openstreetmap.org/#map=13/$lat$/$lon$",
  "text":"Here is a map"
},

The last action is of type map which is populated for latitude and longitude using $lat$ and $lon$ respectively and the zoom value is specified to be 13.

{
  "type":"map",
  "latitude":"$lat$",
  "longitude":"$lon$",
  "zoom":"13"
}

Final output from the server will now contain the three actions with the required data obtained from the respective API calls made. For the sample query `where is london` , the actions will look like :

"actions": [
  {
    "type": "answer",
    "language": "en",
    "expression": "London  is the capital and most populous city of England and the United Kingdom."
  },
  {
    "type": "anchor",
    "link":   "https://www.openstreetmap.org/#map=13/51.51279067225417/-0.09184009399817228",
    "text": "Here is a map",
    "language": "en"
  },
  {
    "type": "map",
    "latitude": "51.51279067225417",
    "longitude": "-0.09184009399817228",
    "zoom": "13",
    "language": "en"
  }
],

This is how the map action responses are generated for location related queries. The complete code can be found at SUSI AI Server Repository.

Resources:

Continue ReadingGenerating Map Action Responses in SUSI AI