{"action":"created","comment":{"url":"https://api.github.com/repos/openscope/openscope/pulls/comments/186292076","pull_request_review_id":117844495,"id":186292076,"diff_hunk":"@@ -1,78 +1,245 @@\n+import _findIndex from 'lodash/findIndex';\n+import _forEach from 'lodash/forEach';\n import _isNil from 'lodash/isNil';\n+import _random from 'lodash/random';\n import RouteModel from '../aircraft/FlightManagementSystem/RouteModel';\n-// import { routeStringFormatHelper } from '../navigationLibrary/Route/routeStringFormatHelper';\n import {\n     isWithinAirspace,\n     calculateDistanceToBoundary\n } from '../math/flightMath';\n import { nm } from '../utilities/unitConverters';\n import { isEmptyObject } from '../utilities/validatorUtilities';\n \n+/**\n+ * Return an array whose indices directly mirror those of `waypointModelList`, except it\n+ * contains the distances along the route at which each waypoint lies from the spawn point\n+ *\n+ * @function _calculateOffsetsToEachWaypointInRoute\n+ * @param waypointModelList {array<WaypointModel>} array of all waypoints along the route\n+ * @return {array<number>} offset distance from spawn point to each waypoint\n+ */\n+function _calculateOffsetsToEachWaypointInRoute(waypointModelList) {\n+    // begin by storing the first fix's offset: a distance of 0 from the spawn point\n+    const waypointOffsetMap = [0];\n+\n+    // continue with second waypoint, because first is already stored\n+    for (let i = 1; i < waypointModelList.length; i++) {\n+        if (waypointModelList[i].isVectorWaypoint || waypointModelList[i - 1].isVectorWaypoint) {\n+            continue;\n+        }\n+\n+        const previousWaypointModel = waypointModelList[i - 1];\n+        const nextWaypointModel = waypointModelList[i];\n+        const distanceToNextWaypoint = previousWaypointModel.calculateDistanceToWaypoint(nextWaypointModel);\n+\n+        waypointOffsetMap.push(distanceToNextWaypoint);\n+    }\n+\n+    return waypointOffsetMap;\n+}\n+\n+/**\n+ * Create an array of the various altitude restrictions along the route, and the offset distances at which they exist\n+ *\n+ * Note that unlike the `waypointOffsetMap`, the altitude offset array DOES NOT mirror the indices\n+ * of the `waypointModelList`. Only offsets and altitudes of altitude-restricted waypoints are included.\n+ *\n+ * @function _calculateAltitudeOffsets\n+ * @param waypointModelList {array<WaypointModel>} array of all waypoints along the route\n+ * @param waypointOffsetMap {array<number>} offset distance from spawn point to each waypoint\n+ * @return {array<array<number>>} [[offsetDistance, altitude], [offsetDistance, altitude], ...]\n+ */\n+function _calculateAltitudeOffsets(waypointModelList, waypointOffsetMap) {\n+    const altitudeOffsets = [];\n+\n+    _forEach(waypointModelList, (waypointModel, waypointIndex) => {\n+        if (!waypointModel.hasAltitudeRestrictions) {\n+            return;\n+        }\n+\n+        altitudeOffsets.push([\n+            waypointOffsetMap[waypointIndex],\n+            Math.min(waypointModel.altitudeMaximum, waypointModel.altitudeMinimum)\n+        ]);\n+    });\n+\n+    return altitudeOffsets;\n+}\n+\n+/**\n+ * Calculate the ideal spawn altitude at the given distance along the route from the spawn point\n+ *\n+ * @function _calculateAltitudeAtOffset\n+ * @param waypointModelList {array<WaypointModel>} array of all waypoints along the route\n+ * @param waypointOffsetMap {array<number>} offset distance from spawn point to each waypoint\n+ * @param altitudeOffsets {array<array<number>>} information about location of altitude restrictions\n+ * @param offsetDistance {number} distance along route at which we want to know the ideal spawn altitude\n+ * @return {number} ideal spawn altitude, in feet (rounded up to nearest thousand)\n+ */\n+function _calculateAltitudeAtOffset(waypointModelList, waypointOffsetMap, altitudeOffsets, offsetDistance) {\n+    const indexOfDistance = 0;\n+    const indexOfAltitude = 1;\n+    const indexOfNextAltitudeRestriction = _findIndex(altitudeOffsets, (altitudeOffset) => {\n+        return altitudeOffset[indexOfDistance] >= offsetDistance;\n+    });\n+    const indexOfPreviousAltitudeRestriction = indexOfNextAltitudeRestriction - 1;\n+\n+    // handle cases where we dont have altitude restrictions ahead AND behind the spawn point\n+    if (indexOfNextAltitudeRestriction < 0) {\n+        if (indexOfPreviousAltitudeRestriction < 0) {\n+            return undefined;\n+        }\n+\n+        return altitudeOffsets[indexOfPreviousAltitudeRestriction][indexOfAltitude];\n+    } else if (indexOfPreviousAltitudeRestriction < 0) {\n+        return altitudeOffsets[indexOfNextAltitudeRestriction][indexOfAltitude];\n+    }\n+\n+    const previousAltitudeRestriction = altitudeOffsets[indexOfPreviousAltitudeRestriction];\n+    const nextAltitudeRestriction = altitudeOffsets[indexOfNextAltitudeRestriction];\n+    const distanceBetweenRestrictions = nextAltitudeRestriction[indexOfDistance] - previousAltitudeRestriction[indexOfDistance];\n+\n+    if (distanceBetweenRestrictions === 0) {\n+        return previousAltitudeRestriction[indexOfAltitude];\n+    }\n+\n+    const altitudeBetweenRestrictions = nextAltitudeRestriction[indexOfAltitude] - previousAltitudeRestriction[indexOfAltitude];\n+    const distanceFromPreviousRestrictionToOffsetDistance = offsetDistance - previousAltitudeRestriction[indexOfDistance];\n+    const progressBetweenRestrictions = distanceFromPreviousRestrictionToOffsetDistance / distanceBetweenRestrictions;\n+    const altitudeChangeFromPreviousRestriction = altitudeBetweenRestrictions * progressBetweenRestrictions;\n+\n+    return previousAltitudeRestriction[indexOfAltitude] + altitudeChangeFromPreviousRestriction;\n+}\n+\n+// function _calculateSpawnPositionsAndAltitudes(waypointModelList, spawnOffsets) {\n+//     const spawnPositions = _calculateSpawnPositions(waypointModelList, spawnOffsets);\n+\n+//     // const altitudeRestrictedWaypoints = waypointModelList.filter((waypointModel) => {\n+//     //     return waypointModel.hasAltitudeRestriction;\n+//     // });\n+\n+//     return _forEach(spawnPositions, (spawnPosition) => {\n+//         spawnPosition.altitude = _calculateAltitudeAtOffset\n+//     });\n+// }\n+\n /**\n  * Loop through `waypointModelList` and determine where along the route an\n  * aircraft should spawn\n  *\n- * @function _calculateSpawnPositions\n- * @param waypointModelList {array<StandardWaypointModel>}\n+ * @function _calculateSpawnPositionsAndAltitudes\n+ * @param waypointModelList {array<WaypointModel>}\n  * @param spawnOffsets {array}\n- * @return spawnPositions {array}\n+ * @return spawnPositions {array<number>} distances along route, in nm\n  */\n-const _calculateSpawnPositions = (waypointModelList, spawnOffsets) => {\n-    const spawnPositions = [];\n+function _calculateSpawnPositionsAndAltitudes(waypointModelList, spawnOffsets) {\n+    const spawnPositionsAndAltitudes = [];\n+    const waypointOffsetMap = _calculateOffsetsToEachWaypointInRoute(waypointModelList);\n+    const altitudeOffsets = _calculateAltitudeOffsets(waypointModelList, waypointOffsetMap);\n \n     // for each new aircraft\n     for (let i = 0; i < spawnOffsets.length; i++) {\n-        // TODO: Are these spawn offsets in nm or km? If not nm, the position generated below\n-        // will be wrong because it expects nautical miles.\n-        let spawnOffset = spawnOffsets[i];\n-\n-        // for each fix ahead\n-        for (let j = 1; j < waypointModelList.length; j++) {\n-            const previousWaypointModel = waypointModelList[j - 1];\n-            const nextWaypointModel = waypointModelList[j];\n-            const distanceToNextWaypoint = previousWaypointModel.calculateDistanceToWaypoint(nextWaypointModel);\n-\n-            if (distanceToNextWaypoint > spawnOffset) {   // if point before next fix\n-                // const previousFixPosition = previousWaypointModel.positionModel;\n-                const heading = previousWaypointModel.calculateBearingToWaypoint(nextWaypointModel);\n-                const spawnPositionModel = previousWaypointModel.positionModel.generateDynamicPositionFromBearingAndDistance(heading, spawnOffset);\n-\n-                // TODO: this looks like it should be a model object\n-                const preSpawnHeadingAndPosition = {\n-                    heading,\n-                    positionModel: spawnPositionModel,\n-                    nextFix: nextWaypointModel.name\n-                };\n-\n-                spawnPositions.push(preSpawnHeadingAndPosition);\n-\n-                break;\n-            }\n-\n-            // if point beyond next fix subtract distance from spawnOffset and continue\n-            spawnOffset -= distanceToNextWaypoint;\n-        }\n+        const spawnOffset = spawnOffsets[i];\n+        //\n+            // for each fix ahead\n+            // for (let j = 1; j < waypointModelList.length; j++) {\n+            //     const previousWaypointModel = waypointModelList[j - 1];\n+            //     const nextWaypointModel = waypointModelList[j];\n+            //     const distanceToNextWaypoint = previousWaypointModel.calculateDistanceToWaypoint(nextWaypointModel);\n+\n+            //     if (distanceToNextWaypoint > spawnOffset) {   // if point before next fix\n+            //         const heading = previousWaypointModel.calculateBearingToWaypoint(nextWaypointModel);\n+            //         const spawnPositionModel = previousWaypointModel.positionModel.generateDynamicPositionFromBearingAndDistance(heading, spawnOffset);\n+\n+            //         // TODO: this looks like it should be a model object\n+            //         const preSpawnHeadingAndPosition = {\n+            //             heading,\n+            //             positionModel: spawnPositionModel,\n+            //             nextFix: nextWaypointModel.name\n+            //         };\n+\n+            //         spawnPositionsAndAltitudes.push(preSpawnHeadingAndPosition);\n+\n+            //         break;\n+            //     }\n+\n+            //     // if point beyond next fix subtract distance from spawnOffset and continue\n+            //     spawnOffset -= distanceToNextWaypoint;\n+            // }\n+\n+        const nextWaypointIndex = _findIndex(waypointOffsetMap, (distanceToWaypoint) => {\n+            return distanceToWaypoint >= spawnOffset;\n+        });\n+        const nextWaypointModel = waypointModelList[nextWaypointIndex];\n+        const previousWaypointIndex = Math.max(0, nextWaypointIndex - 1);\n+        const previousWaypointModel = waypointModelList[previousWaypointIndex];\n+        const distanceFromPreviousWaypointToSpawnPoint = spawnOffset - waypointOffsetMap[previousWaypointIndex];\n+        const heading = previousWaypointModel.calculateBearingToWaypoint(nextWaypointModel);\n+        const spawnPositionModel = previousWaypointModel.positionModel.generateDynamicPositionFromBearingAndDistance(\n+            heading,\n+            distanceFromPreviousWaypointToSpawnPoint\n+        );\n+        const altitude = _calculateAltitudeAtOffset(waypointModelList, waypointOffsetMap, altitudeOffsets, spawnOffset);\n+\n+        spawnPositionsAndAltitudes.push({\n+            altitude,\n+            heading,\n+            nextFix: nextWaypointModel.name,\n+            positionModel: spawnPositionModel\n+        });\n     }\n \n-    return spawnPositions;\n-};\n+    return spawnPositionsAndAltitudes;\n+}\n \n /**\n- * Calculate distance(s) from center where an aircraft should exist onload or airport change\n+ * Calculate distances along spawn pattern route at which to prespawn aircraft\n+ *\n+ * To randomize the spawn locations, the interval between aircraft will vary, but should\n+ * average out to exactly the `entrailDistance`. The exception is if the `entrailDistance`\n+ * is less than the `smallestIntervalNm` defined below. In that case, aircraft will be\n+ * spawned at exactly the `entrailDistance` with no variation due to their proximity.\n+ * \n+ * NOTE: Provided there is at least `smallestIntervalNm` distance between them, an aircraft\n+ * will always be spawned right along the airspace boundary, and another at the first fix.\n+ *\n+ * For example, with `smallestIntervalNm = 15`:\n+ *   - If requesting 8MIT, will spawn exactly 8MIT\n+ *   - If requesting 30MIT, will spawn each a/c 15MIT-45MIT of the previous arrival\n  *\n  * @function _assembleSpawnOffsets\n  * @param entrailDistance {number}\n  * @param totalDistance {number}\n- * @return spawnOffsets {array<number>}\n+ * @return spawnOffsets {array<number>} distances along route, in nm\n  */\n const _assembleSpawnOffsets = (entrailDistance, totalDistance = 0) => {\n-    const spawnOffsets = [];\n+    const offsetClosestToAirspace = totalDistance - 3;\n+    let smallestIntervalNm = 15;\n+    const largestIntervalNm = entrailDistance + (entrailDistance - smallestIntervalNm);\n+\n+    // if requesting less than `smallestIntervalNm`, spawn all AT `entrailDistance`\n+    if (smallestIntervalNm > largestIntervalNm) {\n+        smallestIntervalNm = largestIntervalNm;\n+    }\n+\n+    const spawnOffsets = [offsetClosestToAirspace];\n+    let offset = offsetClosestToAirspace;","path":"src/assets/scripts/client/trafficGenerator/buildPreSpawnAircraft.js","position":267,"original_position":267,"commit_id":"3a81348ba84b24ab37073691ec064d76398e0aae","original_commit_id":"3a81348ba84b24ab37073691ec064d76398e0aae","user":{"login":"n8rzz","id":3325985,"avatar_url":"https://avatars1.githubusercontent.com/u/3325985?v=4","gravatar_id":"","url":"https://api.github.com/users/n8rzz","html_url":"https://github.com/n8rzz","followers_url":"https://api.github.com/users/n8rzz/followers","following_url":"https://api.github.com/users/n8rzz/following{/other_user}","gists_url":"https://api.github.com/users/n8rzz/gists{/gist_id}","starred_url":"https://api.github.com/users/n8rzz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/n8rzz/subscriptions","organizations_url":"https://api.github.com/users/n8rzz/orgs","repos_url":"https://api.github.com/users/n8rzz/repos","events_url":"https://api.github.com/users/n8rzz/events{/privacy}","received_events_url":"https://api.github.com/users/n8rzz/received_events","type":"User","site_admin":false},"body":"Is there a better name here than just offset","created_at":"2018-05-06T14:13:57Z","updated_at":"2018-05-06T14:14:40Z","html_url":"https://github.com/openscope/openscope/pull/952#discussion_r186292076","pull_request_url":"https://api.github.com/repos/openscope/openscope/pulls/952","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/openscope/openscope/pulls/comments/186292076"},"html":{"href":"https://github.com/openscope/openscope/pull/952#discussion_r186292076"},"pull_request":{"href":"https://api.github.com/repos/openscope/openscope/pulls/952"}}},"pull_request":{"url":"https://api.github.com/repos/openscope/openscope/pulls/952","id":185340896,"html_url":"https://github.com/openscope/openscope/pull/952","diff_url":"https://github.com/openscope/openscope/pull/952.diff","patch_url":"https://github.com/openscope/openscope/pull/952.patch","issue_url":"https://api.github.com/repos/openscope/openscope/issues/952","number":952,"state":"open","locked":false,"title":"bugfix/943","user":{"login":"erikquinn","id":5103735,"avatar_url":"https://avatars0.githubusercontent.com/u/5103735?v=4","gravatar_id":"","url":"https://api.github.com/users/erikquinn","html_url":"https://github.com/erikquinn","followers_url":"https://api.github.com/users/erikquinn/followers","following_url":"https://api.github.com/users/erikquinn/following{/other_user}","gists_url":"https://api.github.com/users/erikquinn/gists{/gist_id}","starred_url":"https://api.github.com/users/erikquinn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/erikquinn/subscriptions","organizations_url":"https://api.github.com/users/erikquinn/orgs","repos_url":"https://api.github.com/users/erikquinn/repos","events_url":"https://api.github.com/users/erikquinn/events{/privacy}","received_events_url":"https://api.github.com/users/erikquinn/received_events","type":"User","site_admin":false},"body":"Resolves openscope/openscope#943\r\n\r\nFix bad math somewhere in prespawning to ensure consistently good and randomized spawns on initial load.","created_at":"2018-05-02T05:47:34Z","updated_at":"2018-05-06T14:14:40Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4efe229092742cb0adc60113a5fd2348acb0e314","assignee":{"login":"erikquinn","id":5103735,"avatar_url":"https://avatars0.githubusercontent.com/u/5103735?v=4","gravatar_id":"","url":"https://api.github.com/users/erikquinn","html_url":"https://github.com/erikquinn","followers_url":"https://api.github.com/users/erikquinn/followers","following_url":"https://api.github.com/users/erikquinn/following{/other_user}","gists_url":"https://api.github.com/users/erikquinn/gists{/gist_id}","starred_url":"https://api.github.com/users/erikquinn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/erikquinn/subscriptions","organizations_url":"https://api.github.com/users/erikquinn/orgs","repos_url":"https://api.github.com/users/erikquinn/repos","events_url":"https://api.github.com/users/erikquinn/events{/privacy}","received_events_url":"https://api.github.com/users/erikquinn/received_events","type":"User","site_admin":false},"assignees":[{"login":"erikquinn","id":5103735,"avatar_url":"https://avatars0.githubusercontent.com/u/5103735?v=4","gravatar_id":"","url":"https://api.github.com/users/erikquinn","html_url":"https://github.com/erikquinn","followers_url":"https://api.github.com/users/erikquinn/followers","following_url":"https://api.github.com/users/erikquinn/following{/other_user}","gists_url":"https://api.github.com/users/erikquinn/gists{/gist_id}","starred_url":"https://api.github.com/users/erikquinn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/erikquinn/subscriptions","organizations_url":"https://api.github.com/users/erikquinn/orgs","repos_url":"https://api.github.com/users/erikquinn/repos","events_url":"https://api.github.com/users/erikquinn/events{/privacy}","received_events_url":"https://api.github.com/users/erikquinn/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":509033803,"url":"https://api.github.com/repos/openscope/openscope/labels/BUGFIX","name":"BUGFIX","color":"d93f0b","default":false},{"id":519840198,"url":"https://api.github.com/repos/openscope/openscope/labels/WIP","name":"WIP","color":"006b75","default":false}],"milestone":{"url":"https://api.github.com/repos/openscope/openscope/milestones/13","html_url":"https://github.com/openscope/openscope/milestone/13","labels_url":"https://api.github.com/repos/openscope/openscope/milestones/13/labels","id":3152691,"number":13,"title":"v6.2.0","description":"","creator":{"login":"erikquinn","id":5103735,"avatar_url":"https://avatars0.githubusercontent.com/u/5103735?v=4","gravatar_id":"","url":"https://api.github.com/users/erikquinn","html_url":"https://github.com/erikquinn","followers_url":"https://api.github.com/users/erikquinn/followers","following_url":"https://api.github.com/users/erikquinn/following{/other_user}","gists_url":"https://api.github.com/users/erikquinn/gists{/gist_id}","starred_url":"https://api.github.com/users/erikquinn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/erikquinn/subscriptions","organizations_url":"https://api.github.com/users/erikquinn/orgs","repos_url":"https://api.github.com/users/erikquinn/repos","events_url":"https://api.github.com/users/erikquinn/events{/privacy}","received_events_url":"https://api.github.com/users/erikquinn/received_events","type":"User","site_admin":false},"open_issues":20,"closed_issues":0,"state":"open","created_at":"2018-03-01T06:59:12Z","updated_at":"2018-05-02T08:45:17Z","due_on":"2018-05-28T07:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/openscope/openscope/pulls/952/commits","review_comments_url":"https://api.github.com/repos/openscope/openscope/pulls/952/comments","review_comment_url":"https://api.github.com/repos/openscope/openscope/pulls/comments{/number}","comments_url":"https://api.github.com/repos/openscope/openscope/issues/952/comments","statuses_url":"https://api.github.com/repos/openscope/openscope/statuses/3a81348ba84b24ab37073691ec064d76398e0aae","head":{"label":"openscope:bugfix/943","ref":"bugfix/943","sha":"3a81348ba84b24ab37073691ec064d76398e0aae","user":{"login":"openscope","id":19348939,"avatar_url":"https://avatars0.githubusercontent.com/u/19348939?v=4","gravatar_id":"","url":"https://api.github.com/users/openscope","html_url":"https://github.com/openscope","followers_url":"https://api.github.com/users/openscope/followers","following_url":"https://api.github.com/users/openscope/following{/other_user}","gists_url":"https://api.github.com/users/openscope/gists{/gist_id}","starred_url":"https://api.github.com/users/openscope/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openscope/subscriptions","organizations_url":"https://api.github.com/users/openscope/orgs","repos_url":"https://api.github.com/users/openscope/repos","events_url":"https://api.github.com/users/openscope/events{/privacy}","received_events_url":"https://api.github.com/users/openscope/received_events","type":"Organization","site_admin":false},"repo":{"id":77648370,"name":"openscope","full_name":"openscope/openscope","owner":{"login":"openscope","id":19348939,"avatar_url":"https://avatars0.githubusercontent.com/u/19348939?v=4","gravatar_id":"","url":"https://api.github.com/users/openscope","html_url":"https://github.com/openscope","followers_url":"https://api.github.com/users/openscope/followers","following_url":"https://api.github.com/users/openscope/following{/other_user}","gists_url":"https://api.github.com/users/openscope/gists{/gist_id}","starred_url":"https://api.github.com/users/openscope/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openscope/subscriptions","organizations_url":"https://api.github.com/users/openscope/orgs","repos_url":"https://api.github.com/users/openscope/repos","events_url":"https://api.github.com/users/openscope/events{/privacy}","received_events_url":"https://api.github.com/users/openscope/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/openscope/openscope","description":"openScope Air Traffic Control Simulator","fork":false,"url":"https://api.github.com/repos/openscope/openscope","forks_url":"https://api.github.com/repos/openscope/openscope/forks","keys_url":"https://api.github.com/repos/openscope/openscope/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openscope/openscope/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openscope/openscope/teams","hooks_url":"https://api.github.com/repos/openscope/openscope/hooks","issue_events_url":"https://api.github.com/repos/openscope/openscope/issues/events{/number}","events_url":"https://api.github.com/repos/openscope/openscope/events","assignees_url":"https://api.github.com/repos/openscope/openscope/assignees{/user}","branches_url":"https://api.github.com/repos/openscope/openscope/branches{/branch}","tags_url":"https://api.github.com/repos/openscope/openscope/tags","blobs_url":"https://api.github.com/repos/openscope/openscope/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openscope/openscope/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openscope/openscope/git/refs{/sha}","trees_url":"https://api.github.com/repos/openscope/openscope/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openscope/openscope/statuses/{sha}","languages_url":"https://api.github.com/repos/openscope/openscope/languages","stargazers_url":"https://api.github.com/repos/openscope/openscope/stargazers","contributors_url":"https://api.github.com/repos/openscope/openscope/contributors","subscribers_url":"https://api.github.com/repos/openscope/openscope/subscribers","subscription_url":"https://api.github.com/repos/openscope/openscope/subscription","commits_url":"https://api.github.com/repos/openscope/openscope/commits{/sha}","git_commits_url":"https://api.github.com/repos/openscope/openscope/git/commits{/sha}","comments_url":"https://api.github.com/repos/openscope/openscope/comments{/number}","issue_comment_url":"https://api.github.com/repos/openscope/openscope/issues/comments{/number}","contents_url":"https://api.github.com/repos/openscope/openscope/contents/{+path}","compare_url":"https://api.github.com/repos/openscope/openscope/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openscope/openscope/merges","archive_url":"https://api.github.com/repos/openscope/openscope/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openscope/openscope/downloads","issues_url":"https://api.github.com/repos/openscope/openscope/issues{/number}","pulls_url":"https://api.github.com/repos/openscope/openscope/pulls{/number}","milestones_url":"https://api.github.com/repos/openscope/openscope/milestones{/number}","notifications_url":"https://api.github.com/repos/openscope/openscope/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openscope/openscope/labels{/name}","releases_url":"https://api.github.com/repos/openscope/openscope/releases{/id}","deployments_url":"https://api.github.com/repos/openscope/openscope/deployments","created_at":"2016-12-30T00:32:13Z","updated_at":"2018-05-04T05:17:44Z","pushed_at":"2018-05-05T22:20:12Z","git_url":"git://github.com/openscope/openscope.git","ssh_url":"git@github.com:openscope/openscope.git","clone_url":"https://github.com/openscope/openscope.git","svn_url":"https://github.com/openscope/openscope","homepage":"http://www.openscope.co","size":24604,"stargazers_count":132,"watchers_count":132,"language":"JavaScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":56,"mirror_url":null,"archived":false,"open_issues_count":245,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":56,"open_issues":245,"watchers":132,"default_branch":"develop"}},"base":{"label":"openscope:develop","ref":"develop","sha":"25305bac30cd4d02b8c355dbe0900ee8730f6f11","user":{"login":"openscope","id":19348939,"avatar_url":"https://avatars0.githubusercontent.com/u/19348939?v=4","gravatar_id":"","url":"https://api.github.com/users/openscope","html_url":"https://github.com/openscope","followers_url":"https://api.github.com/users/openscope/followers","following_url":"https://api.github.com/users/openscope/following{/other_user}","gists_url":"https://api.github.com/users/openscope/gists{/gist_id}","starred_url":"https://api.github.com/users/openscope/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openscope/subscriptions","organizations_url":"https://api.github.com/users/openscope/orgs","repos_url":"https://api.github.com/users/openscope/repos","events_url":"https://api.github.com/users/openscope/events{/privacy}","received_events_url":"https://api.github.com/users/openscope/received_events","type":"Organization","site_admin":false},"repo":{"id":77648370,"name":"openscope","full_name":"openscope/openscope","owner":{"login":"openscope","id":19348939,"avatar_url":"https://avatars0.githubusercontent.com/u/19348939?v=4","gravatar_id":"","url":"https://api.github.com/users/openscope","html_url":"https://github.com/openscope","followers_url":"https://api.github.com/users/openscope/followers","following_url":"https://api.github.com/users/openscope/following{/other_user}","gists_url":"https://api.github.com/users/openscope/gists{/gist_id}","starred_url":"https://api.github.com/users/openscope/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openscope/subscriptions","organizations_url":"https://api.github.com/users/openscope/orgs","repos_url":"https://api.github.com/users/openscope/repos","events_url":"https://api.github.com/users/openscope/events{/privacy}","received_events_url":"https://api.github.com/users/openscope/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/openscope/openscope","description":"openScope Air Traffic Control Simulator","fork":false,"url":"https://api.github.com/repos/openscope/openscope","forks_url":"https://api.github.com/repos/openscope/openscope/forks","keys_url":"https://api.github.com/repos/openscope/openscope/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openscope/openscope/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openscope/openscope/teams","hooks_url":"https://api.github.com/repos/openscope/openscope/hooks","issue_events_url":"https://api.github.com/repos/openscope/openscope/issues/events{/number}","events_url":"https://api.github.com/repos/openscope/openscope/events","assignees_url":"https://api.github.com/repos/openscope/openscope/assignees{/user}","branches_url":"https://api.github.com/repos/openscope/openscope/branches{/branch}","tags_url":"https://api.github.com/repos/openscope/openscope/tags","blobs_url":"https://api.github.com/repos/openscope/openscope/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openscope/openscope/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openscope/openscope/git/refs{/sha}","trees_url":"https://api.github.com/repos/openscope/openscope/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openscope/openscope/statuses/{sha}","languages_url":"https://api.github.com/repos/openscope/openscope/languages","stargazers_url":"https://api.github.com/repos/openscope/openscope/stargazers","contributors_url":"https://api.github.com/repos/openscope/openscope/contributors","subscribers_url":"https://api.github.com/repos/openscope/openscope/subscribers","subscription_url":"https://api.github.com/repos/openscope/openscope/subscription","commits_url":"https://api.github.com/repos/openscope/openscope/commits{/sha}","git_commits_url":"https://api.github.com/repos/openscope/openscope/git/commits{/sha}","comments_url":"https://api.github.com/repos/openscope/openscope/comments{/number}","issue_comment_url":"https://api.github.com/repos/openscope/openscope/issues/comments{/number}","contents_url":"https://api.github.com/repos/openscope/openscope/contents/{+path}","compare_url":"https://api.github.com/repos/openscope/openscope/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openscope/openscope/merges","archive_url":"https://api.github.com/repos/openscope/openscope/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openscope/openscope/downloads","issues_url":"https://api.github.com/repos/openscope/openscope/issues{/number}","pulls_url":"https://api.github.com/repos/openscope/openscope/pulls{/number}","milestones_url":"https://api.github.com/repos/openscope/openscope/milestones{/number}","notifications_url":"https://api.github.com/repos/openscope/openscope/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openscope/openscope/labels{/name}","releases_url":"https://api.github.com/repos/openscope/openscope/releases{/id}","deployments_url":"https://api.github.com/repos/openscope/openscope/deployments","created_at":"2016-12-30T00:32:13Z","updated_at":"2018-05-04T05:17:44Z","pushed_at":"2018-05-05T22:20:12Z","git_url":"git://github.com/openscope/openscope.git","ssh_url":"git@github.com:openscope/openscope.git","clone_url":"https://github.com/openscope/openscope.git","svn_url":"https://github.com/openscope/openscope","homepage":"http://www.openscope.co","size":24604,"stargazers_count":132,"watchers_count":132,"language":"JavaScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":56,"mirror_url":null,"archived":false,"open_issues_count":245,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":56,"open_issues":245,"watchers":132,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/openscope/openscope/pulls/952"},"html":{"href":"https://github.com/openscope/openscope/pull/952"},"issue":{"href":"https://api.github.com/repos/openscope/openscope/issues/952"},"comments":{"href":"https://api.github.com/repos/openscope/openscope/issues/952/comments"},"review_comments":{"href":"https://api.github.com/repos/openscope/openscope/pulls/952/comments"},"review_comment":{"href":"https://api.github.com/repos/openscope/openscope/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/openscope/openscope/pulls/952/commits"},"statuses":{"href":"https://api.github.com/repos/openscope/openscope/statuses/3a81348ba84b24ab37073691ec064d76398e0aae"}},"author_association":"OWNER"}}
{"actor":{"display_login":"n8rzz"}}