質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.52%

ReactでGeolocation APIを使用した現在地の取得ができない。

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 319

khkhkh

score 15

前提・実現したいこと

React+Redux+Laravelを使用して個人でアプリケーションを開発しています。
そこで、現在地を取得してGoogleMapsで表示させようと思っているのですが、なぜか現在地がでたらめな場所に表示されてしまいます。関西と関東ほどの誤差があります。
現在地の取得にはcomponentDidMount内でJS標準のGeolocation APIを使用して取得した緯度、経度をstateに保存して使用しています。GoogleMapsの表示に関してはgoogle-map-reactを使用しています。
どなたか解決方法のご教授お願いいたします。

※開発環境にはLaradocを使用しています。
※getGourmet関数に関してはホットペッパーAPIを叩いているだけですので今回は関係ないです。

該当のソースコード

import React from 'react'
import { Link } from 'react-router-dom'
import { withStyles } from '@material-ui/core/styles'
import GoogleMapReact from 'google-map-react'
import * as actions from '../../actions'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'

import Card from '@material-ui/core/Card'
import CardActions from '@material-ui/core/CardActions'
import CardContent from '@material-ui/core/CardContent'
import CardMedia from '@material-ui/core/CardMedia'
import Button from '@material-ui/core/Button'
import AddLocationIcon from '@material-ui/icons/AddLocation'
import EmojiPeopleIcon from '@material-ui/icons/EmojiPeople'
import Replay from '@material-ui/icons/Replay'


const drawerWidth = 250 // Width for Drawer

const styles = (theme) => ({
    root: {
        [theme.breakpoints.up('md')]: {
            width: `calc(100% - ${drawerWidth}px)`,
            marginLeft: drawerWidth
        },
        padding: 30,
    },
    toolbar: theme.mixins.toolbar, // Minimum height of Toolbar
    link: {
        textDecoration: 'none'
    },
    titleArea: {
        margin: '0 auto',
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        flexDirection: 'column'
    },
    title: {
        margin: '0 auto',
        marginBottom: 20,
        padding: 0
    },
    btn: {
        margin: 'auto',
        textAlign: 'center',
    },
    map: {
        width: '90vmin',
        height: '90vmin',
        marginTop: 40,
        marginBottom: 50,
        margin: '0 auto'
    },
    shopListTitle: {
        textAlign: 'center'
    },
    card: {
        [theme.breakpoints.up('sm')]: {
            width: '45%'
        },
        width: '100%',
        margin: 10,
    },
    cardTitle: {
        textDecoration: 'none',
        color: 'black',
    },
    infoTable: {
        borderCollapse: 'collapse'
    },
    info: {
        border: 'solid 1px red',
        padding: 5
    },
    share: {
        textDecoration: 'none',
        color: '#bbb'
    },
    more: {
        textDecoration: 'none',
        color: '#bbb'
    },
    cards: {
        display: 'flex',
        flexWrap: 'wrap',
        margin: '0 auto',
    }
})

const Me = () => <div><EmojiPeopleIcon /></div>
const Pin = () => <div><AddLocationIcon /></div>

class Result extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            lat: null,
            lng: null,
            shops: ''
        }
    }

    render() {
        const { classes } = this.props
        const shops = Array.from(this.state.shops)
        const center = { lat: this.state.lat, lng: this.state.lng }

        // TODO: いいねボタン
        return (
            <div className={classes.root}>
                <div className={classes.toolbar} />
                <div className={classes.titleArea}>
                    <h2 className={classes.title}>周辺のお店</h2>
                    <Link to="/" className={classes.link}><Button className={classes.btn} startIcon={<Replay />} variant="outlined" color="primary">やり直す</Button></Link>
                </div>
                <div className={classes.map}>
                    <GoogleMapReact
                        bootstrapURLKeys={{
                            key: '***********'
                        }}
                        center={center}
                        defaultZoom={14}
                        yesIWantToUseGoogleMapApiInternals
                    >
                        {/* 現在地 */}
                        <Me
                            lat={this.state.lat}
                            lng={this.state.lng}
                        />

                        {/* 店にピンを挿す */}
                        {shops.map((shop, i) => (
                            <Pin
                                key={i}
                                lat={shop.lat}
                                lng={shop.lng}
                            />
                        ))}
                    </GoogleMapReact>
                </div>
                <h2 className={classes.shopListTitle}>お店一覧</h2>
                <div className={classes.cards}>
                    {/* 店の情報を一覧表示 */}
                    {shops.map((shop, i) => (
                        <Card className={classes.card} key={i}>
                            <CardMedia
                                className={classes.media}
                                image={shop.logo_image}
                                title="Contemplative Reptile"
                            />
                            <CardContent>
                                <h2><a href={shop.urls.pc} target="_blank" className={classes.cardTitle}>{shop.name}</a></h2>
                                <p>ー {shop.genre.catch} ー</p>
                                <table className={classes.infoTable}>
                                    <thead>
                                        <tr><td className={classes.info}>アクセス</td><td className={classes.info}>{shop.access}</td></tr>
                                    </thead>
                                    <tbody>
                                        <tr><td className={classes.info}>平均予算</td><td className={classes.info}>{shop.budget.average}</td></tr>
                                        <tr><td className={classes.info}>飲み放題</td><td className={classes.info}>{shop.free_drink}</td></tr>
                                        <tr><td className={classes.info}>食べ放題</td><td className={classes.info}>{shop.free_food}</td></tr>
                                        <tr><td className={classes.info}>最大人数</td><td className={classes.info}>{shop.party_capacity}人</td></tr>
                                        <tr><td className={classes.info}>駐車場</td><td className={classes.info}>{shop.parking}</td></tr>
                                        <tr><td className={classes.info}>open</td><td className={classes.info}>{shop.open}</td></tr>
                                    </tbody>
                                </table>
                            </CardContent>
                            <CardActions>
                                <Button size="small">
                                    <a href={shop.urls.pc} target="_blank" className={classes.more}>Show More</a>
                                </Button>
                            </CardActions>
                        </Card>
                    ))}
                </div>
            </div>
        )
    }

    componentDidMount() {
        navigator.geolocation.getCurrentPosition(pos => {
            this.setState({
                lat: pos.coords.latitude,
                lng: pos.coords.longitude
            })

            const http = axios.create({
                baseURL: 'http://localhost:8000/api/gourmet',
                timeout: 2000,
            })

            this.props.actions.httpRequest()

            // TODO: 関数化?
            const getGourmet = async () => {
                try {
                    const response = await http.get('', {
                        params: {
                            range: 3,
                            order: 4,
                            lat: pos.coords.latitude,
                            lng: pos.coords.longitude,
                            party_capacity: `${this.props.QuestionsReducer.people}`,
                            budget: `${this.props.QuestionsReducer.budget}`,
                            free_food: `${this.props.AmountReducer.freeFood}`,
                            free_drink: `${this.props.AmountReducer.freeDrink}`,
                            genre: `${this.props.QuestionsReducer.genre}`
                        }
                    })
                    this.setState({
                        shops: response.data.results.shop
                    })
                    console.log(response.data.results.shop)
                } catch (error) {
                    console.log(error)
                }
            }

            getGourmet()

            this.props.actions.httpSuccess()
        }, err => {
            this.props.actions.httpFailure()
            console.log(err)
        })
    }
}

const mapStateToProps = state => ({
    QuestionsReducer: state.QuestionsReducer,
    AmountReducer: state.AmountReducer,
    HttpReducer: state.HttpReducer
})

const mapDispatchToProps = dispatch => ({
    actions: bindActionCreators(actions, dispatch)
})

const styledResult = withStyles(styles, { withTheme: true })(Result)

export default connect(mapStateToProps, mapDispatchToProps)(styledResult)
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • jun68ykt

    2019/11/03 23:22

    一点、念のための確認になってしまい恐縮ですが、ご質問に挙げられているコンポーネントを表示させるときの URL は、http ではなくセキュアなhttp すなわち、 https で始まっていますでしょうか?

    キャンセル

  • khkhkh

    2019/11/03 23:47

    ご回答ありがとうございます!
    httpですね!
    httpsでないとセキュリティ的に正しい現在地が取得できないという情報は知っていたのですが、localhostで動作させているということと、一番初めに動作させたときはしっかり位置情報を拾ってくれたので、可能性からは省いてしまったのですが、やはりこれが原因でしょうか?
    当方、知識不足ですので馬鹿なことを言っているかもしれませんが、お付き合い頂けると幸いです…。

    キャンセル

  • jun68ykt

    2019/11/04 00:08

    回答しました。参考になれば幸いです。

    キャンセル

回答 1

checkベストアンサー

+1

こんにちは

MDN の Geolocation API の説明の一番上に、以下のように注記があります。

安全なコンテキスト用

この機能は一部またはすべての対応しているブラウザーにおいて、安全なコンテキスト (HTTPS) でのみ利用できます。

と明記されていますので、https でない限りは、意図した結果にならないと思ったほうがよいと思われます。ちなみにURLスキームが https である場合に、現在地を取得できていることは以下のように確認できます。

Geolocation API のページのURLは、

https://developer.mozilla.org/ja/docs/Web/API/Geolocation/Using_geolocation 

で、URLスキームは https です。このページをスマホのブラウザで表示させ、以下のような黄色の背景に「Show my location」というボタンのあるところまでスクロールします。

イメージ説明

そしてこのボタンをタップすると、位置情報を送信してよいかの許可を求められて、それに了解すると、現在の正確な緯度経度が表示されるかと思いますが、いかがでしょう?(私は iPhone8 を使用し、Safariで開きましたが、正確な現在地の緯度経度が表示されました)

以上、参考になれば幸いです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/04 02:05

    なるほど…。確かに納得いたしました!
    私の調べが足りてませんでしたね…。
    jun68yktさんの回答で全ての疑問が解消されました!
    最後まで拙い質問に付き合って頂き本当にありがとうございます!!
    またご縁があれば是非回答をよろしくお願いいたします!!!

    キャンセル

  • 2019/11/04 02:05 編集

    あ、それらも無事解決致しました!感謝感激です!ありがとうございました!!

    キャンセル

  • 2019/11/04 02:09

    どういたしまして!
    それでは、
    React+Redux+Material-ui onClickイベントがiOSで反応しない
    は自己解決にされるとよいでしょう。
    また次回お役に立てれば幸いです。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.52%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる