Compare commits
10 Commits
24d1f4f2f9
...
f23a1a334d
Author | SHA1 | Date |
---|---|---|
moom0o | f23a1a334d | |
moom0o | 72f766df0e | |
moom0o | a4ab835069 | |
Miguel Carrasco Q | adae1480c5 | |
moom0o | 3d8de1700f | |
moom0o | d29d3f0b11 | |
moom0o | aaa47d0332 | |
moo | c3924c742e | |
moom0o | 3b1e0e0c35 | |
Jeremy Calvert | 693e4c30bf |
|
@ -0,0 +1 @@
|
|||
/node_modules
|
109
README.md
109
README.md
|
@ -5,83 +5,103 @@
|
|||
</div>
|
||||
|
||||
# Overview
|
||||
|
||||
Upload 360° photos to Google Maps without using Google's app.
|
||||
|
||||
The main reason why I created this is because Google Maps isn't a good replacement of the former app. You are only able to upload 360 photos to specific places on the map. With this project, it is possible to upload them at any coordinates.
|
||||
The main reason why I created this is because Google Maps isn't a good replacement of the former app. You are only able
|
||||
to upload 360 photos to specific places on the map. With this project, it is possible to upload them at any coordinates.
|
||||
|
||||
If you want to try it out, there are publicly available instances:
|
||||
| **URL** | **Country** | **Status** | **Hosted By** |
|
||||
| **URL** | **Country** | **Status** | **Hosted By** |
|
||||
|------------------------------------------------|-------------|------------|---------------|
|
||||
| [maps.moomoo.me](https://maps.moomoo.me) | 🇺🇸 | Up | @moom0o |
|
||||
| [map.winscloud.net](https://map.winscloud.net) | 🇹🇭 | Up | @WinsDominoes |
|
||||
| [maps.moomoo.me](https://maps.moomoo.me) | 🇬🇧 | Up | @moom0o |
|
||||
| [map.winscloud.net](https://map.winscloud.net) | 🇹🇭 | Down | @WinsDominoes |
|
||||
|
||||
## Quick start
|
||||
|
||||
In order to get the Google api keys required for the oauth follow these steps:
|
||||
|
||||
### Client ID
|
||||
1) Create a new project at https://console.cloud.google.com/
|
||||
|
||||
2) Head over to the API library and enable the <a href="https://console.cloud.google.com/apis/library/streetviewpublish.googleapis.com">Street View Publish API</a>
|
||||
1. Create a new project at https://console.cloud.google.com/
|
||||
|
||||
3) Select 'Create Credentials', select 'User Data', enter any app name/email.
|
||||
2. Head over to the API library and enable
|
||||
the <a href="https://console.cloud.google.com/apis/library/streetviewpublish.googleapis.com">Street View Publish
|
||||
API</a>
|
||||
|
||||
4) Enable the 'Street View Publish API' scope. You won't need any sensitive scopes.
|
||||
3. Select 'Create Credentials', select 'User Data', enter any app name/email.
|
||||
|
||||
5) Select 'Web application' for type, and name it anything.
|
||||
4. Enable the 'Street View Publish API' scope. You won't need any sensitive scopes.
|
||||
|
||||
6) Add http://localhost:7000 to authorized javascript origins. (If you are running on another domain/port, replace localhost)
|
||||
5. Select 'Web application' for type, and name it anything.
|
||||
|
||||
7) Add http://localhost:7000/auth/ to authorized redirect URIs (If you are running on another domain/port, replace localhost)
|
||||
6. Add http://localhost:7000 to authorized javascript origins. (If you are running on another domain/port, replace
|
||||
localhost)
|
||||
|
||||
8) You should now copy the Client ID into the config.json file.
|
||||
7. Add http://localhost:7000/auth/ to authorized redirect URIs (If you are running on another domain/port, replace
|
||||
localhost)
|
||||
|
||||
8. You should now copy the Client ID into the config.json file.
|
||||
|
||||
### Client Secret
|
||||
Head to the main credentials screen and click the pencil. (Edit OAuth Client) You will be able to find the Client Secret and copy that to your config.json.
|
||||
|
||||
Head to the main credentials screen and click the pencil. (Edit OAuth Client) You will be able to find the Client Secret
|
||||
and copy that to your config.json.
|
||||
|
||||
### Consent Screen
|
||||
1) Select 'OAuth consent screen', use any name/email
|
||||
|
||||
2) Make sure the authorized domain and email is correct, then select 'Save and Continue'.
|
||||
1. Select 'OAuth consent screen', use any name/email
|
||||
|
||||
3) Make sure the Street View Publish API scope is enabled, if not, add it!
|
||||
2. Make sure the authorized domain and email is correct, then select 'Save and Continue'.
|
||||
|
||||
4) For test users, add the email address of the account where you want to upload 360 photos.
|
||||
3. Make sure the Street View Publish API scope is enabled, if not, add it!
|
||||
|
||||
5) **Make sure to also copy the client ID into index.html**, after '&client_id=' and before '&scope', if needed, change the port and domain here as well.
|
||||
4. For test users, add the email address of the account where you want to upload 360 photos.
|
||||
|
||||
5. **Make sure to also copy the client ID into index.html**, after '&client_id=' and before '&scope', if needed, change
|
||||
the port and domain here as well.
|
||||
|
||||
### Config File
|
||||
There are many options in the config file that might confuse you, so here's a simple guide (I think?) to help you get through it.
|
||||
|
||||
| **Keys** | **Default values** | **Usage** |
|
||||
|------------------|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `https` | `false` (boolean) | This is an option whether if you want to enable https or not, this uses boolean values. `false` = http:// and `true` = https:// |
|
||||
| `host` | `localhost` (string) | Your host or domain, if you are hosting on a local machine, set this to `localhost`, if you are hosting this publicly, set it to your domain. |
|
||||
| `port` | `7000` (integer) or `null` | Your port, if are running this as a public instance, set this to `null` |
|
||||
| `openWebBrowser` | `true` (boolean) | This option will open a browser window to PhotoSphereStudio's running instance, set this to `false` if you are running this on a server, |
|
||||
| `clientId` | string | Paste your Google OAuth Client ID here, check the previous steps on how to get it. |
|
||||
| `clientSecret` | string | Paste your Google OAuth Client Secret ID here, check the previous steps on how to get it. |
|
||||
There are many options in the config file that might confuse you, so here's a simple guide (I think?) to help you get
|
||||
through it.
|
||||
|
||||
**INFO: Your authorized JavaScript origins and authorized redirect URIs should be the same as the one you have set in your config file.**
|
||||
| **Keys** | **Default values** | **Usage** |
|
||||
| ---------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `https` | `false` (boolean) | This is an option whether if you want to enable https or not, this uses boolean values. `false` = http:// and `true` = https:// |
|
||||
| `host` | `localhost` (string) | Your host or domain, if you are hosting on a local machine, set this to `localhost`, if you are hosting this publicly, set it to your domain. |
|
||||
| `port` | `7000` (integer) or `null` | Your port, if are running this as a public instance, set this to `null` |
|
||||
| `openWebBrowser` | `true` (boolean) | This option will open a browser window to PhotoSphereStudio's running instance, set this to `false` if you are running this on a server, |
|
||||
| `clientId` | string | Paste your Google OAuth Client ID here, check the previous steps on how to get it. |
|
||||
| `clientSecret` | string | Paste your Google OAuth Client Secret ID here, check the previous steps on how to get it. |
|
||||
|
||||
**INFO: Your authorized JavaScript origins and authorized redirect URIs should be the same as the one you have set in
|
||||
your config file.**
|
||||
|
||||
**Scenario 1 - Public Instance**
|
||||
|
||||
This example is for those who are hosting a public instance, your https, host and port would look something like this. Change `maps.moomoo.me` to your domain.
|
||||
This example is for those who are hosting a public instance, your https, host and port would look something like this.
|
||||
Change `maps.moomoo.me` to your domain.
|
||||
|
||||
You must use a nginx reverse proxy pointing to localhost:7000 for this to work.
|
||||
|
||||
```
|
||||
{
|
||||
"https": true,
|
||||
"host": "maps.moomoo.me",
|
||||
"host": "maps.moomoo.me",
|
||||
"port": 7000
|
||||
}
|
||||
```
|
||||
|
||||
Your **authorized JavaScript origin** would be: `https://maps.moomoo.me`
|
||||
|
||||
Your **authorized redirect URIs** would be: `https://maps.moomoo.me/auth/` (don't forget the slash after `auth`)
|
||||
|
||||
**Scenario 2 - Private Instance**
|
||||
|
||||
This example is for those who are running PhotoSphereStudio on a local machine, your https, host and port would look something like this.
|
||||
This example is for those who are running PhotoSphereStudio on a local machine, your https, host and port would look
|
||||
something like this.
|
||||
|
||||
```
|
||||
{
|
||||
"https": false,
|
||||
|
@ -89,9 +109,34 @@ This example is for those who are running PhotoSphereStudio on a local machine,
|
|||
"port" 7000
|
||||
}
|
||||
```
|
||||
|
||||
Your **authorized JavaScript origin** would be: `http://localhost:7000`
|
||||
|
||||
Your **authorized redirect URIs** would be: `http://localhost:7000/auth/` (don't forget the slash after `auth`)
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/moom0o/PhotoSphereStudio.git
|
||||
```
|
||||
|
||||
2. Install the dependencies
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Update the `config.json` file in the root directory of the project and fill it with the required information (clientId, clientSecret, etc.)
|
||||
|
||||
4. Start the server
|
||||
|
||||
```bash
|
||||
node index.js
|
||||
```
|
||||
|
||||
## Support
|
||||
If you have any questions about how to set this up or about the source code, feel free to ask either **moom0o** (moo#0529 on Discord) or **WinsDominoes** (Win#7206 on Discord).
|
||||
|
||||
If you have any questions about how to set this up or about the source code, feel free to ask either **moom0o** (
|
||||
moo#0529 on Discord) or **WinsDominoes** (Win#7206 on Discord).
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"https": false,
|
||||
"host": "localhost",
|
||||
"host": "localhost",
|
||||
"port": 7000,
|
||||
"openWebBrowser": false,
|
||||
"clientId": "XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com",
|
||||
|
|
17
index.js
17
index.js
|
@ -6,13 +6,13 @@ const openWebBrowser = config.openWebBrowser; // Set to false if running as a se
|
|||
|
||||
let full_url = "";
|
||||
let protocol = "";
|
||||
if(config.https) {
|
||||
if (config.https) {
|
||||
protocol = "https://"
|
||||
} else {
|
||||
protocol = "http://"
|
||||
}
|
||||
|
||||
if(port && !config.https) {
|
||||
if (port && !config.https) {
|
||||
full_url = protocol + host + ":" + port
|
||||
} else {
|
||||
full_url = protocol + host
|
||||
|
@ -59,11 +59,12 @@ app.use(favicon(__dirname + '/public/assets/icons/favicon.ico'));
|
|||
app.get('/', function (req, res) {
|
||||
res.render('pages/index', {
|
||||
full_url: full_url,
|
||||
clientId: clientId
|
||||
clientId: clientId,
|
||||
domain: config.host
|
||||
});
|
||||
})
|
||||
|
||||
app.get('/upload', function(req, res){
|
||||
app.get('/upload', function (req, res) {
|
||||
res.render('pages/upload');
|
||||
})
|
||||
|
||||
|
@ -116,7 +117,7 @@ app.post('/upload', function (req, res) {
|
|||
};
|
||||
request(options, function (error) {
|
||||
if (error) {
|
||||
console.log(error)
|
||||
console.log(error)
|
||||
|
||||
res.status(500).render('pages/error', {
|
||||
errorCode: 500,
|
||||
|
@ -175,9 +176,9 @@ app.post('/upload', function (req, res) {
|
|||
errorCode: JSON.parse(response.body)["error"]["code"],
|
||||
errorStatus: JSON.parse(response.body)["error"]["status"],
|
||||
errorMessage: JSON.parse(response.body)["error"]["message"],
|
||||
response: JSON.stringify(JSON.parse(response.body), null, 4),
|
||||
response: JSON.stringify(JSON.parse(response.body), null, 4),
|
||||
});
|
||||
} else {
|
||||
} else {
|
||||
let shareLink = JSON.parse(response.body)["shareLink"]
|
||||
res.status(200).render('pages/success', {
|
||||
status: JSON.parse(response.body)["mapsPublishStatus"],
|
||||
|
@ -205,7 +206,7 @@ app.get('/auth', function (req, res) {
|
|||
request(options, function (error, response) {
|
||||
if (error) console.log(error) && res.send("Error: Check console");
|
||||
let body = JSON.parse(response.body)
|
||||
if(body["error"] || !body["access_token"]){
|
||||
if (body["error"] || !body["access_token"]) {
|
||||
res.redirect('/')
|
||||
} else {
|
||||
res.cookie('oauth', JSON.parse(response.body)["access_token"], {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"body-parser": "^1.20.1",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"ejs": "^3.1.9",
|
||||
"exif-js": "^2.3.0",
|
||||
"express": "^4.18.2",
|
||||
"express-fileupload": "^1.4.0",
|
||||
"open": "^8.4.0",
|
||||
|
|
|
@ -1,53 +1,63 @@
|
|||
function convertDMSToDD([degrees, minutes, seconds]) {
|
||||
return degrees + minutes / 60 + seconds / 3600;
|
||||
function convertDMSToDD([degrees, minutes, seconds], ref) {
|
||||
decimal_degrees = degrees + minutes / 60 + seconds / 3600;
|
||||
if (ref == 'S' || ref == 'W') {
|
||||
decimal_degrees = -decimal_degrees;
|
||||
}
|
||||
return decimal_degrees;
|
||||
}
|
||||
|
||||
function readURL(input) {
|
||||
if (input.files && input.files[0]) {
|
||||
var reader = new FileReader();
|
||||
if (input.files && input.files[0]) {
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function (e) {
|
||||
$('.image-upload-wrap').hide();
|
||||
reader.onload = function (e) {
|
||||
$('.image-upload-wrap').hide();
|
||||
|
||||
$('.file-upload-image').attr('src', e.target.result);
|
||||
$('.file-upload-content').show();
|
||||
$('.file-upload-image').attr('src', e.target.result);
|
||||
$('.file-upload-content').show();
|
||||
|
||||
$('.image-title').html(input.files[0].name);
|
||||
$('.image-title').html(input.files[0].name);
|
||||
|
||||
var img = document.createElement('img');
|
||||
img.src = e.target.result;
|
||||
var img = document.createElement('img');
|
||||
img.src = e.target.result;
|
||||
|
||||
// Render thumbnail.
|
||||
img.onload = function () {
|
||||
EXIF.getData(img, function () {
|
||||
var allMetaData = EXIF.getAllTags(this);
|
||||
var allMetaDataSpan = document.getElementById('allMetaDataSpan');
|
||||
// allMetaDataSpan.innerHTML = JSON.stringify(allMetaData, null, '\t');
|
||||
var lat = convertDMSToDD(EXIF.getTag(this, 'GPSLatitude'));
|
||||
var long = -convertDMSToDD(EXIF.getTag(this, 'GPSLongitude')); // Negative because it's west longitude
|
||||
$('#lat').val(lat);
|
||||
$('#long').val(long);
|
||||
console.log(`lat: ${lat}, long: ${long}`);
|
||||
});
|
||||
};
|
||||
};
|
||||
// Render thumbnail.
|
||||
img.onload = function () {
|
||||
EXIF.getData(img, function () {
|
||||
var allMetaData = EXIF.getAllTags(this);
|
||||
var allMetaDataSpan = document.getElementById('allMetaDataSpan');
|
||||
// allMetaDataSpan.innerHTML = JSON.stringify(allMetaData, null, '\t');
|
||||
var lat = convertDMSToDD(
|
||||
EXIF.getTag(this, 'GPSLatitude'),
|
||||
EXIF.getTag(this, 'GPSLatitudeRef')
|
||||
);
|
||||
var long = convertDMSToDD(
|
||||
EXIF.getTag(this, 'GPSLongitude'),
|
||||
EXIF.getTag(this, 'GPSLongitudeRef')
|
||||
);
|
||||
$('#lat').val(lat);
|
||||
$('#long').val(long);
|
||||
console.log(`lat: ${lat}, long: ${long}`);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
reader.readAsDataURL(input.files[0]);
|
||||
} else {
|
||||
removeUpload();
|
||||
}
|
||||
reader.readAsDataURL(input.files[0]);
|
||||
} else {
|
||||
removeUpload();
|
||||
}
|
||||
}
|
||||
|
||||
function removeUpload() {
|
||||
$('.file-upload-input').replaceWith($('.file-upload-input').clone());
|
||||
$('.file-upload-content').hide();
|
||||
$('.image-upload-wrap').show();
|
||||
$('.file-upload-input').replaceWith($('.file-upload-input').clone());
|
||||
$('.file-upload-content').hide();
|
||||
$('.image-upload-wrap').show();
|
||||
}
|
||||
|
||||
$('.image-upload-wrap').bind('dragover', function () {
|
||||
$('.image-upload-wrap').addClass('image-dropping');
|
||||
$('.image-upload-wrap').addClass('image-dropping');
|
||||
});
|
||||
|
||||
$('.image-upload-wrap').bind('dragleave', function () {
|
||||
$('.image-upload-wrap').removeClass('image-dropping');
|
||||
$('.image-upload-wrap').removeClass('image-dropping');
|
||||
});
|
||||
|
|
|
@ -8,10 +8,10 @@ var defaultIcon = L.icon({
|
|||
iconUrl: '/assets/icons/marker-icon.png',
|
||||
shadowUrl: '/assets/icons/marker-shadow.png',
|
||||
});
|
||||
|
||||
|
||||
// Creating a map object
|
||||
let map = new L.map('map', mapOptions);
|
||||
|
||||
|
||||
// Creating a Layer object
|
||||
let layer = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
|
@ -25,15 +25,15 @@ map.addLayer(layer);
|
|||
|
||||
// Marker
|
||||
let marker = null;
|
||||
map.on('click', (event)=> {
|
||||
map.on('click', (event) => {
|
||||
|
||||
if(marker !== null){
|
||||
if (marker !== null) {
|
||||
map.removeLayer(marker);
|
||||
}
|
||||
|
||||
marker = L.marker([event.latlng.lat , event.latlng.lng], {icon: defaultIcon}).addTo(map);
|
||||
marker = L.marker([event.latlng.lat, event.latlng.lng], {icon: defaultIcon}).addTo(map);
|
||||
|
||||
document.getElementById('lat').value = event.latlng.lat;
|
||||
document.getElementById('long').value = event.latlng.lng;
|
||||
|
||||
|
||||
})
|
||||
|
|
|
@ -1,139 +1,162 @@
|
|||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
padding: 12px 18px;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background-color: #2196f3;
|
||||
box-shadow: 0 0 4px #999;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
padding: 12px 18px;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background-color: #2196f3;
|
||||
box-shadow: 0 0 4px #999;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-line;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
form, input, label, p {
|
||||
color: white !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
#icon {
|
||||
max-width: 45px;
|
||||
height: auto;
|
||||
border-radius: 50%;
|
||||
max-width: 45px;
|
||||
height: auto;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex-grow: 1;
|
||||
display: inline-block;
|
||||
flex-grow: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Ripple effect */
|
||||
.ripple {
|
||||
background-position: center;
|
||||
transition: background 0.8s;
|
||||
background-position: center;
|
||||
transition: background 0.8s;
|
||||
}
|
||||
|
||||
.ripple:hover {
|
||||
background: #47a7f5 radial-gradient(circle, transparent 1%, #47a7f5 1%) center/15000%;
|
||||
background: #47a7f5 radial-gradient(circle, transparent 1%, #47a7f5 1%) center/15000%;
|
||||
}
|
||||
|
||||
.ripple:active {
|
||||
background-color: #6eb9f7;
|
||||
background-size: 100%;
|
||||
transition: background 0s;
|
||||
background-color: #6eb9f7;
|
||||
background-size: 100%;
|
||||
transition: background 0s;
|
||||
}
|
||||
|
||||
/* DRAG AND DROP */
|
||||
.file-upload {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.file-upload-btn {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
background: #2196f3;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border-bottom: 4px solid #2879f3;
|
||||
transition: all .2s ease;
|
||||
outline: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
background: #2196f3;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border-bottom: 4px solid #2879f3;
|
||||
transition: all .2s ease;
|
||||
outline: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.file-upload-btn:hover {
|
||||
background: #2175f3;
|
||||
color: #ffffff;
|
||||
transition: all .2s ease;
|
||||
cursor: pointer;
|
||||
background: #2175f3;
|
||||
color: #ffffff;
|
||||
transition: all .2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-upload-btn:active {
|
||||
border: 0;
|
||||
transition: all .2s ease;
|
||||
border: 0;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
.file-upload-content {
|
||||
display: none;
|
||||
text-align: center;
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-upload-input {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.image-upload-wrap {
|
||||
margin-top: 20px;
|
||||
border: 4px dashed #2196f3;
|
||||
position: relative;
|
||||
margin-top: 20px;
|
||||
border: 4px dashed #2196f3;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-dropping, .image-upload-wrap:hover {
|
||||
background-color: #2196f3;
|
||||
border: 4px dashed #ffffff;
|
||||
background-color: #2196f3;
|
||||
border: 4px dashed #ffffff;
|
||||
}
|
||||
|
||||
.image-title-wrap {
|
||||
padding: 0 15px 15px 15px;
|
||||
color: #222;
|
||||
padding: 0 15px 15px 15px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.drag-text {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.drag-text .buttonText {
|
||||
font-weight: 100;
|
||||
text-transform: uppercase;
|
||||
color: #fff;
|
||||
padding: 60px 0;
|
||||
font-weight: 100;
|
||||
text-transform: uppercase;
|
||||
color: #fff;
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.file-upload-image {
|
||||
max-height: 200px;
|
||||
max-width: 200px;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
max-height: 200px;
|
||||
max-width: 200px;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.remove-image {
|
||||
width: 200px;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
background: #cd4535;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border-bottom: 4px solid #b02818;
|
||||
transition: all .2s ease;
|
||||
outline: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
width: 200px;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
background: #cd4535;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border-bottom: 4px solid #b02818;
|
||||
transition: all .2s ease;
|
||||
outline: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.remove-image:hover {
|
||||
background: #c13b2a;
|
||||
color: #ffffff;
|
||||
transition: all .2s ease;
|
||||
cursor: pointer;
|
||||
background: #c13b2a;
|
||||
color: #ffffff;
|
||||
transition: all .2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.remove-image:active {
|
||||
border: 0;
|
||||
transition: all .2s ease;
|
||||
border: 0;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
|
|
@ -13,10 +13,11 @@
|
|||
</p>
|
||||
|
||||
<p class="h3 text-danger">
|
||||
<%= errorMessage %>
|
||||
<%= errorMessage %>
|
||||
If you get a NOT_FOUND error, try converting your image to JPEG.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="debugContainer">
|
||||
|
||||
<details>
|
||||
|
@ -33,4 +34,4 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%- include('../partials/footer'); %>
|
||||
<%- include('../partials/footer'); %>
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
|
||||
<div class="alert alert-info" role="alert">
|
||||
<span>You may have to wait awhile after uploading for Google to process the image.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="debugContainer">
|
||||
|
||||
<details>
|
||||
|
|
|
@ -3,68 +3,78 @@
|
|||
<div class="column shadow-sm p-3 m-4 bg-dark-3 text-light">
|
||||
<div align="left">
|
||||
<h1>Upload</h1>
|
||||
<p>This page will allow you to upload photo spheres to Google Maps after the <i>idiots</i> at Google removed their perfectly working StreetView app.</p>
|
||||
|
||||
<p>This page will allow you to upload photo spheres to Google Maps after the <i>idiots</i> at Google removed
|
||||
their perfectly working StreetView app.</p>
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
<span>Images <b>must</b> have the proper 360 degree file properties, you will need to figure out how to add this metadata. <b>Google Pixel photosphere photos work perfectly</b>, I have yet to figure out how to get my DJI drone 360 photos working.</span>
|
||||
<span><b>Use the Hugin program to export DJI Drone PhotoSpheres.</b> Google has <b>removed</b> PhotoSphere from Pixel Camera 9.1 and later, use <a href="https://www.apkmirror.com/apk/google-inc/camera/camera-9-0-115-561695573-37-release/">APKMirror</a> to download older versions!</span>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<span>Latitude and Longitude is <b>not required</b> if your photo already contains location data, however if it doesn't, you can use the map below to pinpoint your location</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form name="uploadForm" action="/upload" method="POST" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<legend>Upload a 360 image</legend>
|
||||
<div class="file-upload">
|
||||
<button class="file-upload-btn" type="button" onclick="$('.file-upload-input').trigger('click')">Add Image</button>
|
||||
|
||||
<div class="image-upload-wrap">
|
||||
<input class="file-upload-input" type='file' id="file" name="file" onchange="readURL(this);" accept="image/png, image/jpeg" />
|
||||
<div class="drag-text">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<span>If your image doesn't contain required metadata, add it yourself with exifdata. .\exiftool.exe -ProjectionType=equirectangular -UsePanoramaViewer=true file.jpeg</span>
|
||||
|
||||
<div class="buttonText">
|
||||
<form name="uploadForm" action="/upload" method="POST" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<legend>Upload a 360 image</legend>
|
||||
<div class="file-upload">
|
||||
<button class="file-upload-btn" type="button" onclick="$('.file-upload-input').trigger('click')">Add
|
||||
Image
|
||||
</button>
|
||||
|
||||
<div class="image-upload-wrap">
|
||||
<input class="file-upload-input" type='file' id="file" name="file" onchange="readURL(this);"
|
||||
accept="image/png, image/jpeg"/>
|
||||
<div class="drag-text">
|
||||
|
||||
<div class="buttonText">
|
||||
|
||||
<span class="material-symbols-outlined" style="font-size: 5vw;">
|
||||
upload
|
||||
</span>
|
||||
|
||||
<h3>Drag and drop a file or select add Image</h3>
|
||||
|
||||
</div>
|
||||
<h3>Drag and drop a file or select add Image</h3>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="file-upload-content">
|
||||
<img class="file-upload-image" src="" alt="Uploaded Image" />
|
||||
<div class="image-title-wrap">
|
||||
<button type="button" onclick="removeUpload()" class="remove-image">Remove <span class="image-title">Uploaded Image</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-upload-content">
|
||||
<img class="file-upload-image" src="" alt="Uploaded Image"/>
|
||||
<div class="image-title-wrap">
|
||||
<button type="button" onclick="removeUpload()" class="remove-image">Remove <span
|
||||
class="image-title">Uploaded Image</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="map" class="text-light">Map</label>
|
||||
<div id="map" class="leaflet-container" style="max-width: 100%; height: 500px"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lat" class="text-light">Latitude</label>
|
||||
<input type="text" id="lat" name="lat" placeholder="Latitude coordinates" class="form-control" required />
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label for="long" class="text-light">Longitude</label>
|
||||
<input type="text" id="long" name="long" placeholder="Longitude coordinates" class="form-control" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="map" class="text-light">Map</label>
|
||||
<div id="map" class="leaflet-container" style="max-width: 100%; height: 500px"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lat" class="text-light">Latitude</label>
|
||||
<input type="text" id="lat" name="lat" placeholder="Latitude coordinates" class="form-control"
|
||||
required/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label for="long" class="text-light">Longitude</label>
|
||||
<input type="text" id="long" name="long" placeholder="Longitude coordinates" class="form-control"
|
||||
required/>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="ripple">UPLOAD</button>
|
||||
</form>
|
||||
<button type="submit" class="ripple">UPLOAD</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<%- include('../partials/footer'); %>
|
||||
<%- include('../partials/footer'); %>
|
||||
|
|
|
@ -1,27 +1,28 @@
|
|||
|
||||
|
||||
|
||||
<p class="text-center text-light">Source Code: <a href="https://github.com/moom0o/PhotoSphereStudio/">Github</a> - Made by <a href="https://github.com/moom0o">moom0o</a> & <a href="https://github.com/WinsDominoes">Win</a></p>
|
||||
|
||||
</div>
|
||||
<p class="text-center text-light">Source Code: <a href="https://github.com/moom0o/PhotoSphereStudio/">Github</a> - Made
|
||||
by <a href="https://github.com/moom0o">moom0o</a> & <a href="https://github.com/WinsDominoes">Win</a></p>
|
||||
|
||||
<script src="/assets/drag-and-drop.js"></script>
|
||||
</div>
|
||||
|
||||
<!-- LEAFLET JS -->
|
||||
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js" integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" crossorigin=""></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/leaflet.locatecontrol/dist/L.Control.Locate.min.js" charset="utf-8"></script>
|
||||
<script src="/assets/leaflet/js/map.js"></script>
|
||||
<script src="/assets/drag-and-drop.js"></script>
|
||||
|
||||
<!-- Optional JavaScript -->
|
||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
|
||||
|
||||
<!-- EXIF JS -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.js" integrity="sha512-bE9sOOprru4D2nRKDQSNvhdxw47Jd0r2s7QThKB/KkU5HEPuxsk9DbK+fYIMCYlCsXkwp6M87vj84sAtYWqxvw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<!-- LEAFLET JS -->
|
||||
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"
|
||||
integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" crossorigin=""></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/leaflet.locatecontrol/dist/L.Control.Locate.min.js" charset="utf-8"></script>
|
||||
<script src="/assets/leaflet/js/map.js"></script>
|
||||
|
||||
<!-- Then Material JavaScript on top of Bootstrap JavaScript -->
|
||||
<script src="/assets/material/material.min.js"></script>
|
||||
<!-- Optional JavaScript -->
|
||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
|
||||
|
||||
<!-- EXIF JS -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.js"
|
||||
integrity="sha512-bE9sOOprru4D2nRKDQSNvhdxw47Jd0r2s7QThKB/KkU5HEPuxsk9DbK+fYIMCYlCsXkwp6M87vj84sAtYWqxvw=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<!-- Then Material JavaScript on top of Bootstrap JavaScript -->
|
||||
<script src="/assets/material/material.min.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -9,34 +9,38 @@
|
|||
<link rel="stylesheet" href="/assets/style.css">
|
||||
|
||||
<!-- Add Material font (Roboto) and Material icon as needed -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,500,500i,700,700i|Roboto+Mono:300,400,700|Roboto+Slab:300,400,700" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,500,500i,700,700i|Roboto+Mono:300,400,700|Roboto+Slab:300,400,700"
|
||||
rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Add Material CSS, replace Bootstrap CSS -->
|
||||
<link href="/assets/material/material.min.css" rel="stylesheet">
|
||||
|
||||
<!-- GOOGLE ICONS -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0" />
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0"/>
|
||||
|
||||
<!-- LEAFLET FOR MAP -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin="" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet.locatecontrol/dist/L.Control.Locate.min.css" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"
|
||||
integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin=""/>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet.locatecontrol/dist/L.Control.Locate.min.css"/>
|
||||
|
||||
<title>PhotoSphereStudio - Upload 360° Photos to Google Maps</title>
|
||||
</head>
|
||||
<body class="bg-dark-2">
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-info">
|
||||
<a class="navbar-brand" href="/">
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-info">
|
||||
<a class="navbar-brand" href="/">
|
||||
|
||||
<span>
|
||||
<img src="/assets/icons/icon.png" alt="Icon" id="icon">
|
||||
PhotoSphereStudio
|
||||
</span></a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div align="center">
|
||||
<div align="center">
|
Loading…
Reference in New Issue