Wednesday, June 20, 2018

Switched to Project Fi and Save Some Money!

For the longest time until October 2017, I was a faithful T-Mobile subscriber for my mobile phone. They offered the perfect combination of package that I need: 5 GB of high-speed data (it was 3G, 4G, and then LTE over-time), unlimited SMS, and some non-unlimited voice (100 minutes) - all for $30 per month. I was on that plan for years (along with my wife's phone). So we are paying around $64 per month for our cellphone bills combined. We both rarely talk over the phone, most of our communication is happening via data (SMS, WhatsApp, Slack, Skype, etc), and we also have a home phone (VOIP) that we use for long-calling (like calling family members, customer supports, etc etc that are minutes intensive).

I have heard about Project Fi when it came out - somehow it was not that attractive for me at the time. Until I have to buy a new phone ...

So what is Project Fi? It is basically Google's phone carrier - but instead of using their own towers etc, Google uses three existing providers (T-Mobile, Sprint, US Cellular) to provide the subscribers their mobile data services. Project Fi approved devices will be able to switch among all the providers seamlessly. Regular GSM devices will be able to use T-Mobile service only. Project Fi is also a PREPAID service - so there is no contract, no cancellation fee, etc. Which is awesome!

How does it save me money? I thought paying ~$30 per month is cheap already!? Project Fi simplifies your billing - so basically the main account holder must pay $20 per month for the main line - which includes unlimited talk and text. Now on top of it, you only pay for the data that you use with rate $10 per GB. So if I am only using .5GB this month, I will only billed for ~$25 before tax. I don't know about you - but in my day-to-day, 80-90% of the time I am always connected to WiFi (home, office, my friend's house, free WiFi at a coffee shop/restaurant, etc) - and I have my stats too from the last year of my T-Mobile which shows that on average that I only use around 0.7GB of LTE data per month. With that, I am potentially saving $5-$7 per month - not much, I know - but I gain so much more (better coverage, unlimited call, better support, and simplified billing).

Then ... Google released this app call Datally. You can read more about it here. Which basically allows you to control data usage on an app-by-app. The default setting is to not allow data if the app is not on the foreground. So by installing this app, my LTE data usage actually goes down from ~0.7GB to ~0.5GB per month.

Better with more people! It gets better! I switched my wife's provider from T-Mobile to Project Fi - now because she is not the main account holder (her account is working under my account), she is only billed $15 per month (for unlimited calls and text) instead of $20 (like me because I am the main/primary account). Then our data usage is combined and billed together. So what this means is that if we are assuming that her data usage is similar like me (between 0.5-0.7GB of LTE data per month), at the end of the month our combined cell phone bill is ~$49 ($20 + $15 + $7 + $7) before tax - which is ~$12-$14 saving per month. Not much, but we'll take it.

Bill Protection Benefit! Project Fi has other advantages - such as: "Bill Protection" - which is a cap of your bill (not your data usage), but a cap of actually how much Google can actually bill you. The cap is $60 for your LTE data. So if you are on a single account plan, that would $80 total ($20 phone & text + $60 data). Or a different way to look at it is this is the "Unlimited Plan" for Project Fi subscriber.

Free International SMS & Data! With Project Fi, you can use SMS and data freely (already included) in around 170 countries. You can also make or receive phone calls, but it is not free.

You can read more about Project Fi here.
-- read more and comment ...

Sunday, June 17, 2018

Drawing Multiple Routes In One Map with Google Map

Lots of examples in Google Map tutorials and examples are about drawing directions, markers, searches - and all of them typically involve only drawing one set of markers into a "route". An example of this will be going from Boston, MA to Seattle, WA via Cleveland, OH then Chicago, IL. But I need to be make Google Map to draw multiple routes in a single map.

The purpose of this task is to compare the routes. Assuming, like the example above, I am going from Boston to Seattle - which route will be better? Or maybe closer to passing Detroit?

Or another use case: if my friend and I are both traveling out of NYC on 2 separate cars, I am going to Columbus, OH and my friend is going to Detroit, MI - and another friend needs to be dropped off in State College, PA - so which car should this friend join - my car or the other car?

So here is how you do it.
FIRST - you will need to get Google Map Javascript API Key - you can get it here. Once you get it and enable the needed APIs, you will need to use it in your script reference below.

SECOND - in your HTML markup - add script reference Google Map and substituting the YOUR_API_KEY with your real API key from above.
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
    #map {
        margin-top: 10px;
        height: 750px;
        width: 100%;
    }
</style>
</head>
<body>
 <div id="map"></div>
 <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap" type="text/javascript"></script>
</body>
</html>
THIRD - initialize several global variables and create a javascript method called "initMap" - this is to initialize your map once the script from Google is loaded.
var routeMapArray = [];
var map;
var defaultZoom = 11;
var directionsService;
var directionDisplayRenderEngineArray = [];
var iconColor = '';

function initMap() {
  routeMapArray = [];
  directionsService = new google.maps.DirectionsService();

  // center initially on NYC
  loadMap(new google.maps.LatLng(40.7128, -74.0060));
}
FOURTH - Create a method called "loadMap" that takes 1 parameter and create a json payload for your routes.
var loadMap = function(center) {
  var mapOptions = {
    zoom: 11,
    mapTypeControl: false,
    streetViewControl: false,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };

  if (center !== undefined && center !== null) {
    mapOptions.center = center;
  }

  // Some basic map setup (from the API docs)
  map = new google.maps.Map(document.getElementById('map'), mapOptions);

  // Start the request making
  var requestArray = [{
      "origin": "New York, NY",
      "destination": "Seattle, WA",
      "waypoints": [{
          "location": "Detroit, MI",
          "stopover": true
        },
        {
          "location": "Denver, CO",
          "stopover": true
        }
      ],
      "travelMode": "DRIVING"
    },
    {
      "origin": "Boston, MA",
      "destination": "San Diego, CA",
      "waypoints": [{
          "location": "Dayton, OH",
          "stopover": true
        },
        {
          "location": "Las Vegas, NV",
          "stopover": true
        }
      ],
      "travelMode": "DRIVING"
    }
  ];
  processRequests(requestArray);
};
FIFTH - We need to process the requests in order, one by one - using different renderer each time on the same map (this is the key).
var processRequests = function(requestArray) {
  var bounds = new google.maps.LatLngBounds();
  var infoWindow = new google.maps.InfoWindow();

  // Counter to track request submission and process one at a time;
  var i = 0;
  var position = "";

  // Used to submit the request 'i'
  var submitRequest = function() {
    if (requestArray.length === 0) return;
    directionsService.route(requestArray[i], processDirectionResults);
  };

  // Used as callback for the above request for current 'i'
  function processDirectionResults(result, status) {
    if (status === google.maps.DirectionsStatus.OK) {
      // Create a unique DirectionsRenderer 'i'
      directionDisplayRenderEngineArray[i] = new google.maps.DirectionsRenderer();
      directionDisplayRenderEngineArray[i].setMap(map);

      if (i == 0) {
        bounds = new google.maps.LatLngBounds();
      }
      iconColor = "#000000";

      directionDisplayRenderEngineArray[i].setOptions({
        preserveViewport: true,
        polylineOptions: {
          strokeWeight: 5,
          strokeOpacity: 0.8,
          strokeColor: iconColor
        },
        markerOptions: {}
      });

      // Use this new renderer with the result
      directionDisplayRenderEngineArray[i].setDirections(result);

      // adjust zooming
      if (i === 0) {
        bounds = directionDisplayRenderEngineArray[i].getDirections().routes[0].bounds;
      } else {
        bounds.union(directionDisplayRenderEngineArray[i].getDirections().routes[0].bounds);
      }

      map.fitBounds(bounds);
      if (map.zoom > defaultZoom) {
        map.setZoom(defaultZoom);
      }

      // and start the next request
      setTimeout(function() {
        nextRequest();
      }, 500);
    }
  }

  function nextRequest() {
    // increment counter
    i++;

    // next request if any
    if (i >= requestArray.length) {
      // No more to do
      return;
    }

    // submit next request
    submitRequest();
  }

  // begin processing
  submitRequest();
};
SIXTH - Done. Make. profit. Here is a working JSFiddle for it.
-- read more and comment ...